Vue.js

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


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:

  1. Backend receives request
  1. retrieves / computes data
  1. generates HTML file

Frontend templates are reactive (work through binding):

Model changes (triggered by user or backend) \rarr Value changes \rarr DOM update

single page application

A single fetch from backend

Vue.js

Vue.js is a JavaScript Framework.

Ways to import

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">
&rarr;
<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:

  1. mounted()
    • after DOM has been fully rendered for component (but maybe not all child components!)
  1. 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)

UsesVuexlibrary.https://vuex.vuejs.org/

Managing a shared global state between all components.

No direct access to global state:

  1. Committing Mutations: (Synchronous functions)

    ie. calling store.commit('increment') triggers a predefined increment mutation.

  1. 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},
]