Vue.js
https://www.youtube.com/watch?v=gbw8dyJT9AI
https://www.youtube.com/watch?v=Wy9q22isx3U
Concepts
declarative
Using templates instead of imperatively defining DOM changes command by command.
Using binding to define relationships between DOM and data.
reactive
Reacting to user input changes / events.
MVVM
stands for:
- Model (Data) Plain JavaScript Objects
- ViewModel (Logic) Vue → Keeps View and Model in synch
- View (DOM) Output / Design
Front-End vs. Back-End Templating
Templating = seperating View and ViewModel.
Backend templates work through request - response:
- Backend receives request
- retrieves / computes data
- generates HTML file
Frontend templates are reactive (work through binding):
Model changes (triggered by user or backend) Value changes DOM update
single page application
A single fetch from backend
Vue.js
Vue.js is a JavaScript Framework.
CLI commands
Create project
vue create <projectName>
vue ui
→ graphical uiProject setup
npm install
Compiles and hot-reloads for development
npm run serve
Compiles and minifies for production
npm run build
Lints and fixes files
npm run lint
Ways to import
As library - directly in html
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><div id="app"> Hello, {{ course }} <- View </div><script> var app = new Vue({ <- ViewModel el: '#app', data: { <- Model course: 'Web Engineering' } }) </script>
As javascript framework
Components are organized in
.vue
Files (transpiled to JavaScript in build process).<template> <div id="app"> Hello, {{ course }} </div> </template><script> export default { name: 'App', data: function() { <- Model is a function because this is a component return { course: 'Web Engineering' } }, } </script><style> #app { font-family: Arial; color: #2c3e50; } </style>
Components
Consist of
<template>
HTML code, declares binding
<script>
Behaviour of ViewModel
<style>
scoped style, bound to this component
The script consists of
props
Constructor of this component
computed
Computed values
component
registered sub components of this template
methods
functions for event handling
mounted,...
life cycle methods
One-Way Binding
v-bind
Interpolation Syntax
{{ data }}
As attribute
<img v-bind:src="standardImage">
<img :src="standardImage">
(syntactic sugar)
<template>
<article>
<h2>{{recipe.title}}</h2>
...
<img :src="standardImage">
</article>
</template>
<script>
export default {
name: "RecipeItem",
props: {
recipe: {
title: String,
thumbnail: String,
url: String,
ingredients: Array
}
},
data: function() {
return {
standardImage :
"standard.jpg"
}
}
}
</script>
Two-Way Binding
v-model
Not possible for properties, direct binding to parent causes maintainability issues
The same as one way binding + emitting events.
As attribute
<input v-model="ingredientInput">
<template>
<section id="search">
<form …>
<label for="ingredients">
Ingredients
</label>
<input v-model="ingredientInput"
type="text" name="ingredients" />
<button type="submit">Search</button>
</form>
</section>
...
</template>
Computed Properties
Derived properties from model (data).
Should only
- contain simple operations
- be synchronous (will be cache, lazy reevaluated)
<template>
<article>
<h2>{{recipe.title}}</h2>
...
How many? {{ ingredientCount }}!
</article>
</template><script>
export default {
name: "RecipeItem",
props: {
recipe: {
title: String,
thumbnail: String,
url: String,
ingredients: Array
}
},
computed : {
ingredientCount : () => {
return this.ingredients.length;
}
}
}
</script>
Requires getters and setters for two way binding:
computed : {
ingredients : {
get: function() {
return this.ingredientInput.split(',');
},
set: function(ingredients) {
this.ingredientInput = ingredients.join(',');
}
}
}
Conditional rendering in Templates
v-if
,
v-else
,
v-else-if
<template>
<article>
<h2>{{recipe.title}}</h2>
<div>
<div>
<img v-if="recipe.thumbnail"
:src="recipe.thumbnail">
<img v-else :src="standardImage">
<template v-if="recipe.url">
→
<a :href="recipe.url">
Full Recipe
</a>
</template>
</div>
</article>
</template>
Bounded loops
v-for
,
v-bind:key
Index is provided by iterator if needed<li v-for="(item, idx) in items" :key="idx">
<template>
...
<h3>Ingredients</h3>
<ul class="ingredients">
<li v-for="ingredient in recipe.ingredients" :key="ingredient.name> <- key must be unique!
{{ ingredient }}
</li>
</ul>
...
</template>
Events and Methods
v-on, @event_name
Reacting to DOM events and adding event listeners .
<button v-on:click="recipeSearch()">
<button @click="alert('Hello!')">
(syntactic sugar)
Event modifiers
postfixes that change behaviour
ie:
v-on:submit.prevent
Most used events:
onChange
,onInput
<template>
<div id="recipeSearch"><section id="search">
<form role="search" v-on:submit.prevent="recipeSearch()"> <- prevent default behaviour
<label for="ingredients">Ingredients</label>
<input v-model="ingredientInput" type="text" name="ingredients"/>
<button type="submit">Search</button>
</form>
</section><section id="results">
<recipe-item v-for="(recipe, index) in recipes" :key="index" :recipe="recipe"/>
</section></div>
</template><script>
export default {
...
methods : {
recipeSearch : async function() {
this.recipes = ...
}
}
...
}
</script>
Custom Events
this.$emit(event_name, 'data')
get emitted with
this.$emit('ingredientAdd')
Can be used to send data to parents.
<recipe-item v-on:ingredientAdd="addIngredientToSearch"/>
<recipe-item @ingredientAdd="addIngredientToSearch"
v-for="(recipe, index) in recipes" :key="index"
:recipe="recipe" />
<template>
...
<h3>Ingredients</h3>
<ul class="ingredients">
<li @click="addIngredient(ingredient)"
v-for="ingredient in recipe.ingredients"
:key="ingredient.name">
{{ ingredient }}
</li>
</ul>
...
</template>
<script>
...
methods : {
addIngredient : function(ingredient) {
this.$emit("ingredientAdd", ingredient);
}
}
...
</script>
Lifecycle Hooks
Only two hooks important for this Lecture:
mounted()
- after DOM has been fully rendered for component (but maybe not all child components!)
created()
- before DOM has been loaded
- access to data, computed properties, methods, etc
- Used to trigger actions like fetching data from backends
State Management (Vuex)
UsesVuex
library.https://vuex.vuejs.org/
Managing a shared global state between all components.
No direct access to global state:
-
Committing Mutations:
(Synchronous functions)
ie. calling
store.commit('increment')
triggers a predefined increment mutation.
-
Actions:
(Can be Asynchronous)
Can be read in components
this.$store.dispatch('incrementCart')
Routing / Navigation
For single page applications.
URL fragments allow linking while on the same browser page:
www.example.com/#/coolStuff
const routes = [
{ path: '/', redirect: '/search'},
{ path: '/search', component: Search},
{ path: '/cart', component: Cart},
{ path: '/checkout', component: Checkout},
{ path: '/config/:artworkId', component: Config, props: true },
{ path: '*', component: PageNotFound},
]