DOBROMIR HRISTOV
We will build a simple multi page Vue SPA in the form of a dynamic map of Westeros from Game of Thrones.
The map will be populated by the 9 great houses, each in its respective place.
Each house will have a drop down menu with a few of its main members.
Each member will have their own personal page.
git clone https://github.com/dobromir-hristov/vue-beginner-workshop.git
....
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
{{ vueText }}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
vueText: 'Hello Vue'
}
})
</script>
</body>
</html>
<div id="app">
Hello Vue
</div>
<div id="app">
<div>
{{ vueText }}
</div>
<input type="text" v-model="vueText">
</div>
<div v-if="isVisible === true"> That's hot! </div>
Expression Body
Directive Name
<div v-if="false">
Invisible div?
</div>
<div v-show="false">
Invisible div?
</div>
<!---->
<div style="display: none">
Invisible div?
</div>
Conditionally display elements in the DOM
Hides the element
Removes the element
<div class="pepper" v-for="chilli in chillies">
{{ chilli }}
</div>
// the Vue Instance
data: {
chillies: [
'Cayenne',
'Habanero',
'Jalapeno',
'Ghost Pepper',
'Trinidad Moruga Scorpion',
'Carolina Reaper'
]
}
Dynamically binds DOM element attributes to data on the Vue instance
<button :disabled="isSaving === true">
Save Form
</button>
<button disabled="disabled">
Save Form
</button>
v-bind:property has a shorthand as just :property
data: {
isSaving: true
}
<button :disabled="isActive" :class="userEnergyLevel">
Get in Shape
</button>
<div :class="{ active: isActive }"> Sports! </div>
<div :class="[ favoriteSport, isActive ? 'active': 'passive' ]">
Activity
</div>
<!-- assign multiple at once -->
<div v-bind="{ class: { active: isActive }, title: 'Healthy' }">
Running is fun.
</div>
<button disabled="disabled" class="low">
Get in Shape
</button>
<div class="active"> Sports! </div>
<div class="rugby active">
Activity
</div>
<!-- assign multiple at once -->
<div class="active" title="Healthy">
Running is fun.
</div>
data: {
isActive: true,
userEnergyLevel: 'low',
favoriteSport: 'rugby'
}
Creates a two way bond between a form element and data property on the instance
<form>
<label>Recipe Name</label>
<input v-model="form.name" type="text">
<label>Portions</label>
<input
v-model="form.portions"
type="number">
<label>Vegan
<input
v-model="form.isVegan"
type="checkbox">
</label>
<label>Difficulity</label>
<input
v-model="form.difficulity"
type="range">
</form>
<button @click="counter++">
Increment
</button>
<div class="vueDog vuePet"
@click="vueDog = !vueDog">
<img v-if="vueDog"
src="vueDog.jpeg">
<div class="text"> Tap the doggy </div>
</div>
<div class="vueReprtile vuePet"
@mouseenter="vueReptile = true"
@mouseleave="vueReptile = false">
<img
v-show="vueReptile"
src="vueReptile.jpg">
<div class="text"> Touch the gecko </div>
</div>
<div class="vueReprtile vuePet"
@mousedown="vueLeo = true"
@mouseup="vueLeo = false">
<div class="text"> Keep finger on the leo </div>
<img v-show="vueLeo"
src="https://image.ibb.co/cD9xMV/Vue-Leopard.jpg">
</div>
<input type="text" v-model="username">
<input
:value="username"
type="text"
@input="username = $event.target.value"
>
Intelligently handles different form element components by listening and handling data in the required way
<input type="checkbox" v-model="isVisible">
<input
:checked="isVisible"
type="checkbox"
@change="isVisible = $event.target.checked"
>
Expand under the hood to
Show the .members dropdown when clicking on a house
Make templates easier to read and manage.
Can be used inside moustache expressions or dynamic bindings
data: {
counter: 0
},
methods: {
increment() {
this.counter = this.counter + 1
}
}
<button @click="increment">Add one</button>
<div>{{ counter }}</div>
<button @click="increment">+1 boringness</button>
<button @click="incrementBy(10)">+10 boringness</button>
<button @click="incrementTimes(5)">x5 boringness</button>
data: {
boringMeter: 0
},
methods: {
increment(){
this.incrementBy(1)
},
incrementBy(number = 0){
this.boringMeter = this.boringMeter + number
},
incrementTimes(times = 2) {
this.boringMeter = this.boringMeter * times
}
}
// ...
data: {
firstName: 'John',
lastName: 'Doe'
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
},
<div>{{ fullName }}</div>
<div>John Doe</div>
<input v-model="search" type="text">
<div
v-for="chilli in filteredChillies"
:key="chilli"
class="pepper"
>
{{ chilli }}
</div>
data: {
search: "",
chillies: [ "Cayenne", "Habanero", ... ]
},
computed: {
filteredChillies() {
return this.chillies.filter(chilly => {
return chilly
.toLowerCase()
.includes(this.search)
}
);
}
var counter = {
template: '<button @click="increment">{{ count }}</button>',
data() {
return {
count: 0
}
},
methods: {
increment(){
this.count = this.count + 1
}
}
}
Vue.component('VCounter', counter) // Global component registration
<div>
<v-counter></v-counter>
</div>
var VCounter = {
template: '<button @click="increment">{{ count }}</button>',
data() {
return {
count: 0
}
},
methods: {
increment(){
this.count = this.count + 1
}
}
}
new Vue({
el: '#app',
components: {
VCounter: VCounter // Local Component registration
}
})
var component = {
data: () => ({ title: '' }),
template: '<div class="someClass">{{title}}</div>'
}
<script type="text/x-template" id="unique-id">
<div class="someClass">
{{ title }}
</div>
</script>
<script>
var component = {
data: () => ({ title: '' }),
template: '#unique-id'
}
</script>
String or template literal
X-template template tag
const component = {
data: () => ({ title: '' }),
render() {
return <div class="someClass">{ this.title }</div>
}
})
JSX
const component = {
data: () => ({ title: '' }),
render(createElement) {
return createElement('div', {
class: ['someClass']
},
this.title)
}
}
Render Function
<counter :min="5" :step="5" :max="100"></counter>
props: ['PropA', 'PropB']
props: {
PropA: Object,
PropB: String
}
props: {
PropA: {
type: Object,
required: true
},
PropB: {
type: String,
default: 'Lorem Ipsum'
}
}
Shortest - mostly for demos
Slightly Longer
Longest - recommended, most detailed
<v-counter :increment-by="5"></v-counter>
{
template: '<button @click="increment">{{ count }}</button>',
props: {
incrementBy: {
type: Number,
default: 1
}
},
data() {
return { count: 0 }
},
methods: {
increment(){
this.count = this.count + this.incrementBy
}
}
}
Props are considered immutable to child components
Default value for a prop of type Object or Array, it should be a factory function returning the required data.
Use as kebab-case in templates, but as camelCase inside methods and computed properties on the component.
Way for child components to communicate with parent components.
template: '<button @click="notifyParent">click me</button>',
data: () => ({ count:0 }),
methods: {
notifyParent() {
this.$emit('change', this.count)
}
}
<counter @change="handleChange"/>
methods: {
handleChange(count) {
// handle event
}
}
Parent Component
Child Component
{
props: {
incrementBy: {
type: Number,
default: 1
},
value: {
type: Number,
required: true
}
},
methods: {
increment(){
const value = this.value + this.incrementBy
this.$emit('input', value)
}
}
}
<v-counter
:increment-by="5"
:value="currentCount"
@input="handleInput"
></v-counter>
Child Component
Parent Component
{
data() {
return {
currentCount: 0
}
},
methods: {
handleInput(emittedValue){
this.currentCount = emittedValue
}
}
}
<button @click="increment">{{value}}</button>
// 1. Define some routes
const routes = [
{ path: '/', component: HomePage },
{ path: '/user/:id', name: 'users', component: UserPage, props: true }
]
// 2. Pass the routes
const router = new VueRouter({
routes: routes
})
var app = new Vue({
el: '#app',
router: router
})
website.com/user/:id
website.com/user/1
<div id="app">
<the-header></the-header>
<div class="main-wrapper">
<router-view></router-view>
</div>
<the-footer></the-footer>
</div>
<div id="app">
<the-header></the-header>
<div class="main-wrapper">
<!-- child is injected -->
<user-page></user-page>
<!-- parent template continues -->
</div>
<the-footer></the-footer>
</div>
<router-link to="/about">Go to About</router-link>
<router-link :to="{ name: 'about' }">
Go to About
</router-link>
<router-link :to="{ name: 'user', params: { id: 1 } }">
Go to About
</router-link>
<a href="/about">Go to About</a>
<a href="/about">Go to About</a>
<a href="/user/1">Go to User 1</a>
Move the main Vue instance template and functionality to a new WesterdosMapPage component.
Assing it as a page with name of home and / path.
npm install -g @vue/cli
vue create my-project
# OR
vue ui
<template>
<button @click="inc">{{ count }}</button>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
inc(){
this.count++
}
}
}
</script>
<style lang="scss">
.button {
background: red;
&:hover {
background: pink;
}
}
</style>
All of the above are solved by Vue CLI out of the box.