Claudio Bisconti
 

mail: claudio.bisconti@comm-it.it
linkedin: linkedin.com/in/claudiobisconti
github: github.com/cbspire

About me

  • Sales Account
  • Technical Leader
  • Fullstack software developer

@

What I know

Vue.js

Created by Evan You (Ex Google)

First release in February 2014

Actually used by

  • Laravel
  • Alibaba
  • Xiaomi
  • Netflix 
  • Gitlab
  • Facebook 🤫
  • and more others

Vue.js

/vjuː/  => { 'view' }

  • Progressive framework
  • for building user interfaces
  • Modern and lightweight approach
  • Components oriented
  • with declarative rendering
  • and reactivity approach
  • where models are simple Javascript object

Vue.js initialization

var vm = new Vue({
  el: '#vue-app',
  data: {
    meetup: {
      name: 'Awesome Vue Vol.1',
      availability: true
    }
  }
})
<div id="vue-app">
  <div>
    <h2>{{ meetup.name }}</h2>
    <button v-if="meetup.availability">
        Join now
    </button>
  </div>
</div>

HTML

Javascript

Declarative rendering like Angular

'new Vue' define a Vue instance

We can change availability from:
vm.meetup.availability = false

Components

Components logic

Toolbar

Sidebar

Content / Routing?

Footer

List of Contents

News

Form Login

Avatar

Drawer from the left

MyApp

News

News

Components logic

Study before write

  • Before start we need to study the architecture
  • ... and the interactions

 

Divide et Impera

  • You need to simplify your workflow
  • In this phase, you can kill your UI/UX designer
    (the judge will undestand)

 

Delegate

  • Every component must have only one goal 

 

.vue files

single file component

<template>
    <!-- Component template -->
</template>
<script>
export default {
    name: 'ComponentName', 
    // use: <component-name> ...
}
</script>
<style>
    // Component style
</style>

<template> tag

Used to render your component

 

<script> tag

Used to write the logic of component

 

<style> tag

Styling your component

It needs webpack, I'm sorry 🙁

.vue files

alternative solution

<!-- File .vue -->

<template>
    <!-- Component template -->
</template>

<script src="./component.js"></script>
<style src="./component.css"></style>

One important thing to note is that separation of concerns is not equal to separation of file types.
https://vuejs.org/v2/guide/single-file-components.html#What-About-Separation-of-Concerns

It needs webpack again, I'm sorry 🙁

Component registration

import MyComponent from '../components/my-component'

export default {
    ...
    components: {
        MyComponent
    }
}

Global: Declaration of intent 😇 

Local

<!-- Global Registration -->

Vue.component('my-component-name', { /* ... */ })

Vue <template>

<template>
    <v-app>
        <v-navigation-drawer app></v-navigation-drawer>
        <v-toolbar app></v-toolbar>
        <v-content>
            <v-container fluid>
                <router-view></router-view>
            </v-container>
        </v-content>
        <v-footer app></v-footer>
    </v-app>
</template>

We can also use a render function or JSX like React

https://vuejs.org/v2/guide/render-function.html

Only one rule:

You must have a single root component

Sample vuetify structure

Template syntax

<template>
  <template v-if="meetup.step == 'template-syntax'">
      Boring
  </template>
  <template v-else-if="meetup.step == 'computed-properties'">
      Awesome!
  </template>
  <template v-else>
      Not bad <!-- I hope -->
  </template>
</template>

v-if

<ul>
  <li v-for="item in items" :key="item.id">
      {{ item.name }}
  </li>
</ul>

v-for

Template syntax

<template>
  <div v-show="error">
    <modal-dialog title="Error"></modal-dialog>
  </div>
</template>

v-show

<div>
  <h1 v-once>
    {{ title }}
  </li>
</div>

v-once (one time interpolation)

<template>
  <div v-html="htmlTextVariable"></div>
</template>

v-html

Bindings

<div>

  <div class="actions">
      <button v-on:click="myClickHandler"> CLICK ME </button>
      <button @click="myClickHandler"> CLICK ME </button>
  </div>

  <div class="contents">
      <p> {{ myTextData }} </p>
      <p v-html="myHtmlData"></p>
  </div>

  <div class="forms">
      <button v-bind:id="myDynamicId">Used? Mah</button>
      <input type="text" v-bind:disabled="inputIsDisabledValue" value="" />
      <input type="password" :value="passwordData" :class="dynamicClasses" />
  </div>

  <div class="expressions">
      <p>{{ ok ? 'YES' : 'NO' }}</p>
      <p>{{ number + 1 }}</p>
      <p>{{ array.join(',') }}</p>


      <p>{{ if (ok) { return message } }}</p> <!-- It doesn't work even if you pray -->
  </div>

</div>

Forms && v-model

<div>
    <form>
        <input v-model="username" type="text">
        <input v-model="email" type="text">
        <textarea v-model="message" placeholder=""></textarea>
    </form>
</div>

Modifiers

<div>
    <form>
        <input v-model.lazy="username" type="text" @change="checkUserExists">
        <input v-model.trim="email" type="text">
        <input v-model.number="age" type="number">
    </form>
</div>

<template> slots

<template>
    <div class="flex">
        <avatar :src="user.image" />
        <div class="content">
            <p> {{ user.name }}</p>
            <slot></slot>
        </div>
    </form>
</template>

Sample template Component UserCard

<div>
    <user-card :user="user" />
    <user-card :user="user">
        <p>{{ user.description }}</p>
    </user-card>
</div>

Template usage

<script> tag

<script>
import Avatar from './avatar'
import _ from 'lodash'

export default {
    name: 'UserCard',  // <user-card>
    components: { Avatar },
    props: ['user'],
    data() {
        return {
            notificationsCount: 0,
            showDropdown: false
        }
    },
    computed: {
        initials () {
            return this.user.first_name.substr(0,1) + this.user.last_name.substr(0,1)
        }
    },
    methods: {
        getNotifications(id) {
            fetch(...).then(result => { this.notificationsCount = result.data.count });
        }
    },
    watch: {
        user: (newValue, oldValue) => {
            ...
        }
    }
    mounted() {
        this.getNotifications(this.user.id)
    }
}
</script>

Componet lifecycle

Props

used for passing data from parent to child

export default {
  props: [
    'user',
    'size',
    'notificationCount'
  ]
}

Props

used for passing data from parent to child

export default {
  props: { 
    user: Object,
    size: String,
    notificationCount: Number
  }
})

Prop Types

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

Props

used for passing data from parent to child

export default {
  props: { 
    user: {
      type: Object,
      required: true,
      default: () => {}
    },
    size: {
      type: String,
      required: true,
      validator (value) {
        return ['small', 'normal', 'large', 'big'].includes(value)
      },
      default: 'normal'
    },
    notificationCount: {
        type: Number,
        validator (value) {
          return value >= 0
        },
        default: 0
    }
  }
})

Prop Types

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

Data

internal property of component

export default {
    ...
    data() {
        return {
            notificationsCount: 0,
            showDropdown: false
        }
    },
}

Data must be a function,
otherwise changing a data property affect the data of all other instances

https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function

Methods

functions inside components

export default {
    data() {
        notificationsCount: 0,
        showDropdown: false
    },
    methods: {
        getNotifications(id) {
            fetch(...).then(result => {
                this.notificationsCount = result.data.count
            });
        },
        toggle() {
            this.showDropdown = !this.showDropdown
        }
    },
    mounted() {
        this.getNotifications(this.user.id)
    }
}
export default {
    data() {
        notificationsCount: 0,
        showDropdown: false
    },
    methods: {
        async getNotifications(id) {
            let result = await fetch(...)
            this.notificationsCount = result.data.count

        },
        toggle() {
            this.showDropdown = !this.showDropdown
        }
    },
    mounted() {
        this.getNotifications(this.user.id)
    }
}

Invoking Methods

from template or script

<template>
    <div>
        <button @click="onClick">CLICK ME</button>
        <ul>
            <li v-for="item in items" :key="item.id">
                <button @click="onItemClick(item)">CLICK ME</button>
            </li>
        </ul>
    </div>
</template>

export default {
    methods: {
        onClick() {
            // ...
        },
        onItemClick(item) {
            // ...
        },
        initSomething() {
            // ...
        }
    },
    mounted() {
        this.initSomething(this.items.length)
    }
}

Computed properties

reactive functions, similar to methods

However, the difference from methods is that computed properties are cached based on their dependencies

https://vuejs.org/v2/guide/computed.html

<template>
    <avatar-circle :initials="initials" />
</template>

<script>
export default {
    props: ['user', 'size', 'notificationCount'],
    computed: {
        initials () {
            return this.user.first_name.substr(0,1) +
                   this.user.last_name.substr(0,1)
        }
    }
}
</script>

Computed properties

How it works

function Archiver() {
  var temperature = null;
  var archive = [];

  Object.defineProperty(this, 'temperature', {
    get() {
      console.log('get!');
      return temperature;
    },
    set(value) {
      temperature = value;
      archive.push({ val: temperature });
    }
  });

  this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

Object.defineProperty

🕐

Computed properties

<template>
  <input type="text" v-model="search" />
  <ol>
    <li v-for="user in filteredUsers" :key="user.id"> 
        {{ user.last_name }} {{ user.first_name }}
    </li>
  </ol>
</template>


<script>
import _ from 'lodash'

export default {
  name: 'UserCard',
  props: ['users'],
  data() {
    return {
      search: ''
    }
  },
  computed: {
    filteredUsers() {
      const search = this.search.toLowerCase().trim()

      return _.filter(this.users, (user) => {
          return _.startsWith(user.last_name.toLowerCase(), search)
      });
    }
  }
}
</script>

Computed properties

<template>
  <div :class="dynamicClass">
    <img :src="user.image" class="rounded" />
    <div class="content"> {{ fullName }} </div>
  </div>
</template>









<script>
export default {
  name: 'UserCard',
  props: ['user', 'size'],
  computed: {
    dynamicClass() {
      return {
        'flex': true,
        'user': this.user,
         [`role-${this.user.role}`]: this.user,
         [`size-${this.size}`]: this.size
      }
    }
  }
}
</script>
let user = {
  role: 'admin',
  image: 'http://lorempixel.com/200/200/',
  fullName: 'Claudio Bisconti'
}


<user-card :user="user" size="large" />

<!--
<div class="flex user role-admin size-large">
  <img src="http://lorempixel.com/200/200/" class="rounded">
  <div class="content"> Claudio Bisconti </div>
</div>
-->

Watchers

<template>
    <div>
        <input type="text" v-model="search" />
        <p v-if="warning">You must write more then 3 characters</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            search: '',
            warning: false,
        }
    },
    watch: {
        search(newValue, oldValue) {
            this.warning = newValue.length < 3
        }
    }
}
</script>

While computed properties are more appropriate in most cases, there are times when a custom watcher is necessary.

https://vuejs.org/v2/guide/computed.html#Watchers

Watchers

<template>
    <div>
        <input type="text" v-model="search" />
        <p> {{ this.waiting }} </p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            search: '',
            waiting: ''
        }
    },
    watch: {
        search(newValue, oldValue) {
            this.waiting = 'Waiting for you to stop typing...'
            this.debounceGetAnswer()
        }
    },
    created: function () {
        this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
    },
    methods: {
        getAnswer() {
            this.answer = 'Trying connection...'
            // ...fetch, search and whatever you want
        }
    }
}
</script>

<style> tag

let you decorate your component

<style>
...
</style>

<style lang="scss">
...
</style>

<style scoped>
...
</style>

<style>
...
</style>

<style src="./file.css"></style>

<style scoped> tag

let you decorate your component

<template>
  <div class="example">
    hi
  </div>
</template>

<style scoped>
.example {
  color: red;
}
</style>
<template>
  <div class="example" data-v-f3f3eg9>
    hi
  </div>
</template>

<style>
.example[data-v-f3f3eg9] {
  color: red;
}
</style>

Vue.js Tools

vue-cli 3

$ vue create awesome-vue

$ yarn serve

$ yarn prod
  • Interactive project scaffolding
  • Zero config rapid prototyping
  • Extensible via plugin
  • Webpack freeeee (se... magari!)

vue ui 😱

​"na figata pazzesca" Cit.

Demo (real)time?

Conclusions 🤭

Thanks

<template>
  <div>
    <h1>THANKS FOR YOUR ATTENTION</h1>
    <h2 v-if="count >= 0">
        Questions: {{ count }}
    </h2>
    <template v-else>
        <h2>No? Great!</h2> 
        <add-question :onSubmit="addQuestion"></add-question>
    </template>
  </div>
</template>

<script>
import AddQuestion from './add-question'

export default {
    name: 'QuestionSlide',
    components: { AddQuestion },
    data: () => {
        return {
            count: 0,
            questions: []
        }
    },
    methods: {
        addQuestion(question) {
            this.questions.push(question)
        }
    }
}
</script>

Awesome Vue Vol.2

What I think to do

  • Vue router
  • Vuex
  • Events communication
  • Custom v-model inputs
  • Large application structure
  • Unit testing

 

Bonus pack:

  • How to Build a cordova app from vue-cli in 30 seconds (download excluded)

Awesome Vue Vol.1

By Claudio Bisconti

Awesome Vue Vol.1

A simple introduction to Vue.js

  • 1,275