Personal learning application to understand Vue.js. Step by step approach
- Quick introduction
- Cheatsheet
- Installation
- Using a CDN
- Using NPM
- Debugging
- Visual Studio code - Extensions
- Options
- Options / Data
- Options / DOM
- Options / Lifecycle Hooks
- Options / Assets
- Options / Composition
- Options / Misc
- Instance Properties
- Instance Methods / Data
- Instance Methods / Events
- Instance Methods / Lifecycle
- Directives
- Special Attributes
- Built-In Components
- Important remarks
- Laravel
- Webpack
- Tips
- Working with forms
- Playing with Vue
- Test1 - Minimal application
- Test2 - Binding attribute
- Test3 - Conditional rendering
- Test4 - List rendering
- Test5 - Event handling
- Test6 - Class and style binding
- Test7 - Computed properties
- Test8 - Migrate to component
- Test9 - Communicating events
- Test10 - Forms
- Test11 - Tabs
- Test12 - Todos list
- Test13 - Tasks components
- Test14 - Show modal
- Test15 - Slots
- Test16 - Inline templates
- Test17 - Watch
- Test18 - Webpack - Vue cli
- Test19 - Single Page Application
- Tutorials
- CONTINUER
- website: https://vuejs.org/
- Vue mastery tutorials: https://www.vuemastery.com/courses/intro-to-vue-js/
- The one followed for this repository (tests 1 till 11): https://www.vuemastery.com/courses/intro-to-vue-js/vue-instance/
- laracast tutorials: https://laracasts.com/series/learn-vue-2-step-by-step/
- A curated list of awesome things related to Vue.js: https://github.com/vuejs/awesome-vue
- vuejs on Github: https://github.com/vuejs
- French tutorial: https://fr.vuejs.org/v2/guide/
- French videos - Grafikart: https://www.youtube.com/playlist?list=PLjwdMgw5TTLW-mAtlR46VajrKs4dep3y0
- 30 days of Vue (PDF and tutorials) https://github.com/fullstackio/30-days-of-vue
- Vue is Reactive: the Vue instance data object is linked to every places where this data is used so, by assigning a new value to a data, Vue will guarantee that the newest value will be displayed in the DOM. No coding is required.
Easy way, just include the .js file by adding a reference to the unpkg CDN: https://unpkg.com/vue@2.5.16/dist/vue.js
Read more: https://fr.vuejs.org/v2/guide/installation.html#CDN
From a DOS prompt, type:
npm install Vue --save
then in the main.js
, where we'll use Vue, just add import Vue like this:
import Vue from "vue";
Note: when the HTML is accessed by file://a_file.html, we'll need to change the configuration of the add-on and allow file access URL
.
In the options of the add-on, we'll need to Allow access to file URLs
.
https://laracasts.com/series/learn-vue-2-step-by-step/episodes/2 introduce the add-on and how we can use it; from the Chrome console.
- Vetur - Syntax highlighting for
.vue
files- Github: https://github.com/vuejs/vetur
- Marketplace Visual Studio: https://marketplace.visualstudio.com/items?itemName=octref.vetur
- Prettier - Script beautifier
- ESLint: Linting utility for Javascript and JSX
List of options: https://vuejs.org/v2/api/#Options-Data
Options are set in the Vue()
constructor, here below, el
and data
.
var app = new Vue({
el: "#app",
data: {
[...]
}
});
Here below a short list of options:
https://vuejs.org/v2/api/#computed
Where to define computed values...
var app = new Vue({
el: "#app",
data: {
firstname: "Christophe",
lastname: "Avonture"
},
computed: {
fullname() {
return firstname + " " + lastname;
}
}
});
https://vuejs.org/v2/api/#data
Where to declare variables and default values.
var app = new Vue({
el: "#app",
data: {
firstname: "Christophe"
}
});
https://vuejs.org/v2/api/#props
Where to declare properties that can be initialized by the parent (caller) and used by the component.
var app = new Vue({
el: "#app",
props: {
message: {
type: String,
required: true,
default: "Hello world"
}
}
});
https://vuejs.org/v2/api/#methods
Where to code functions and events
<button @click="showTitle">Show the title</button>
var app = new Vue({
el: "#app",
data: {
title: "My preferred socks"
},
methods: {
showTitle() {
alert(this.title);
}
}
});
https://vuejs.org/v2/api/#watch
Set a watcher to a variable. The function will be called every time the data is changed.
<div id="app" class="container">
<h1>Edit the title and see the console</h1>
<input type="text" v-model="title" />
</div>
var app = new Vue({
el: "#app",
data: {
title: "My preferred socks"
},
watch: {
title: function(val, oldVal) {
console.log("new title: %s, old value: %s", val, oldVal);
}
}
});
Set the DOM element associated to the Vue instance
<div id="app">
<tasks></tasks>
</div>
var app = new Vue({
el: "#app"
});
https://vuejs.org/v2/api/#template
Template string that will replace the DOM element
<div id="app">
<task>This is a task</task>
</div>
Vue.component("task", {
template: `<div class="message-header">
<p><slot></slot></p>
</div>`
});
https://vuejs.org/v2/api/#render
Vue recommend to use template
but, when we need javascript for creating our HTML. So, a render
function can be used instead of template
.
See https://vuejs.org/v2/guide/render-function.html
A nice example is given here: https://vuejs.org/v2/guide/render-function.html#Basics
https://vuejs.org/v2/api/#renderError
Provide a new way of outputting errors. Only since Vue 2.2.0 and during local development mode.
new Vue({
render(h) {
throw new Error("oops");
},
renderError(h, err) {
return h("pre", { style: { color: "red" } }, err.stack);
}
}).$mount("#app");
There are a lot of events: https://vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- activated
- deactivated
- beforeDestroy
- destroyed
- errorCaptured
- vm.$attrs
- vm.$children
- vm.$data
- vm.$el
- vm.$isServer
- vm.$listeners
- vm.$options
- vm.$parent
- vm.$props
- vm.$refs
- vm.$root
- vm.$scopedSlots
- vm.$slots
- [vm.$destroy()](https://vuejs.org/v2/api/#vm-destroy
- vm.$forceUpdate()
- vm.$mount( [elementOrSelector] )
- vm.$nextTick( [callback] )
By coding in a Blade template in Laravel, the following syntax WON'T WORK while it's perfectly correct:
<div id="app">
<ul>
<li v-for="detail in details">{{ detail }}</li>
</ul>
</div>
This is because the {{ ... }}
syntax will be intercepted by Laravel (thus on server level (PHP
)) while, here, it's not a Laravel expression but a Vue expression (thus on server side (javascript
)).
==> We need to inform Laravel to ignore it ! This is done by adding the @
character before the expression:
<div id="app">
<ul>
<li v-for="detail in details">@{{ detail }}</li>
</ul>
</div>
Alternative: here we can use the v-text
attribute to avoid to use an expression:
<li v-for="detail in details" v-text="detail"></li>
When building the package, Webpack will inject <script src="/dist/build.js"></script>
in the index.html
file. The location of the build.js
file will thus be set to the root of the web application and not relative to the current folder.
To use a relative path, we'll need to modify the webpack.config.js
file and remove the /
before the dist folder in the publicPath
variable:
module.exports = {
[ ... ]
output: {
[ ... ]
publicPath: 'dist/',
[ ... ]
},
Laracast episodes 19, 20 and 21 is giving nice example of how create a Vue.js form submit script working with Laravel validation.
Tutorial: The Vue instance Same minimal approach and really good explained on https://laracasts.com/series/learn-vue-2-step-by-step/episodes/1
- Creating an
app
DOM object and put in thereexpression
by using the{{ ... }}
syntax. - In JS, create a Vue instance that refers to the
app
DOM element. Put in thedata
store an attribute calledproduct
and assign a value.
During the HTML rendering, Vue will replace in the app
div any expression that refers to product
by his value.
Tip: in the developer console, we can reassign a new value to app.product
. Vue will ensure that the DOM will reflect the change.
<div id="app">
<h2>{{ product }}</h2>
<p>I really like {{ product }}...</p>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
product: "Socks"
}
});
</script>
We can bind HTML attributes thanks to v-bind
like this:
<div id="app">
<div class="product">
<img v-bind:src="image" v-bind:alt="alt" v-bind:title="title" />
</div>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
image: "assets/images/Socks-blue.png",
alt: "a pair of socks",
title: "My preferred socks"
}
});
</script>
The shorthand for v-bind:
is :
<img :src="image" :alt="alt" :title="title" />
Display DOM elements based on a condition like:
<div id="app">
<div class="product-info">
<h2>{{ product }}</h2>
<p v-if="inStock">In Stock</p>
<p v-else>Out of Stock</p>
</div>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
product: "Socks",
inStock: true
}
});
</script>
v-if="inStock"
will just compare true
or false
but the expression can be more complex like v-if="inStock > 10"
(when lower or equal to 10, we'll display Out of stock
f.i.).
<div id="app">
<div class="product-info">
<h2>{{ product }}</h2>
<p v-if="inStock > 10">In Stock</p>
<p v-else-if="inStock > 0">Almost sold out!</p>
<p v-else>Out of Stock</p>
</div>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
product: "Socks",
inStock: true
}
});
</script>
Remark: With v-if
, when the condition isn't met, the DOM element is removed from the page. By displaying the rendered HTML, paragraphs with "Almost sold out!" and "Out of Stock" aren't in the DOM when products are "In Stock". With v-show
the DOM is just displayed or not with CSS (display: none;
when the condition isn't met).
The v-for
syntax will traverse an array and, for each item, will create a DOM element.
The example here below will create bullets:
<div id="app">
<div class="product-info">
<h2>{{ product }}</h2>
<ul>
<li v-for="detail in details">{{ detail }}</li>
</ul>
</div>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
product: "Socks",
details: ["80% cotton", "20% polyester", "Gender-neutral"]
}
});
</script>
And here one DIV by variants:
<div id="app">
<div class="product-info">
<h2>{{ product }}</h2>
<div v-for="variant in variants" :key="variant.variantId">
<p>{{ variant.variantColor }}</p>
</div>
</div>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
product: "Socks",
variants: [
{
variantId: 2234,
variantColor: "green"
},
{
variantId: 2235,
variantColor: "blue"
}
]
}
});
</script>
Notes:
- it is recommended to use a special key attribute when rendering elements like above so that Vue can keep track of each node's identity.
- another syntax is
<li v-for="detail in details" v-text="detail"></li>
: with v-text, we can specify the text to display without using an expression like{{ detail }}
.
Just using the syntax v-on:
followed by the event and a valid expression.
Below a Add to cart
button. Each time we'll click on it, the cart
variable will be incremented and, since Vue is reactive, each time a value is changed, every place where that value is used is automatically updated so, without need of code, the paragraph <p>Cart({{cart}})</p>
will be updated.
<div id="app">
<div class="product-info">
<h2>{{ product }}</h2>
<button v-on:click="cart +=1">Add to Cart</button>
<div class="cart"><p>Cart({{cart}})</p></div>
</div>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
product: "Socks",
cart: 0
}
});
</script>
While <button v-on:click="cart +=1">Add to Cart</button>
is valid, it'll be better to use a function like <button v-on:click="addToCart">Add to Cart</button>
<div id="app">
<div class="product-info">
<h2>{{ product }}</h2>
<button v-on:click="addToCart">Add to Cart</button>
<div class="cart"><p>Cart({{cart}})</p></div>
</div>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
product: "Socks",
cart: 0
},
methods: {
addToCart: function() {
this.cart += 1;
}
}
});
</script>
The shorthand for v-on:
is @
.
ES6 Syntax Instead of writing out an anonymous function like addToCart: function()
, we can use the ES6 shorthand and just say addToCart()
. These are equivalent ways of saying the same thing.
The code below will display the image by using data.image
thus a green sock.
<div id="app">
<div class="product">
<div class="product-image"><img v-bind:src="image" /></div>
</div>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
product: "Socks",
image: "assets/images/Socks-green.png"
}
});
</script>
We'll display a list of variations i.e. the fact that the socks are available in green or blue and, when the user will move the mouse cursor over a color, we'll dynamically replace the image by the colored one.
<div id="app">
<div class="product">
<div class="product-image"><img v-bind:src="image" /></div>
<div class="product-info">
<div v-for="variant in variants" :key="variant.variantId">
<p v-on:mouseover="updateProduct(variant.variantImage)">
{{ variant.variantColor }}
</p>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
product: "Socks",
image: "assets/images/Socks-green.png"
variants: [
{
variantId: 2234,
variantColor: "green",
variantImage: "assets/images/Socks-green.png"
},
{
variantId: 2235,
variantColor: "blue",
variantImage: "assets/images/Socks-blue.png"
}
]
},
methods: {
updateProduct(imgSrc) {
this.image = imgSrc;
}
}
});
</script>
The v-bind
can also be applied to a style
attribute but, then, we don't must to use the double {{ ... }}
but a single one { ... }
.
Below, the code will add two divs, one with a blue background and one with a green.
<div id="app">
<div class="product">
<div class="product-image"><img v-bind:src="image" /></div>
<div class="product-info">
<div
v-for="variant in variants"
:key="variant.variantId"
class="color-box"
:style="{ backgroundColor: variant.variantColor }"
></div>
</div>
</div>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
product: "Socks",
image: "assets/images/Socks-green.png"
variants: [
{
variantId: 2234,
variantColor: "green"
},
{
variantId: 2235,
variantColor: "blue"
}
]
}
});
</script>
HTML disabled attribute can be easily set like this:
<button v-on:click="addToCart" :disabled="!inStock">Add to Cart</button>
This will add the disabled="disabled"
attribute as soon as data.inStock
is false.
<script>
var app = new Vue({
el: "#app",
data: {
inStock: false
}
});
</script>
For applying a special CSS class depending on the boolean result (true / false):
<button
v-on:click="addToCart"
:disabled="!inStock"
:class="{ disabledButton: !inStock}"
>
Add to Cart
</button>
Vue offers computed
data in a very easy way:
<div id="app">
<div class="product">
<div class="product-info"><h2>{{ titleProduct }}</h2></div>
</div>
</div>
<script src="https://unpkg.com/vue@2"></script>
<script>
var app = new Vue({
el: "#app",
data: {
brand: "Master banch",
product: "Socks"
},
computed: {
titleProduct() {
return this.brand + " " + this.product;
}
}
});
</script>
We can now modify, separately, app.brand
or app.product
, the computed titleProduct
variable will be keep up-to-date by Vue.
Note: the computed value is cached so only rerun when a value is changed.
Instead of using a inStock
static value, we can use a computed value like below.
This code will display two DIVs (coming from app.data.variants
, one with a green background (index=0) and one with a blue (index=1).
Thanks the mouseover
event, the index will be captured (0 or 1) and the updateProduct
and the index will be used to initialize app.data.selectedVariant
to 0 or 1.
Due to a data has been modified, Vue will recalculate the value of inStock
and then reevaluate this.variants[this.selectedVariant].variantQuantity
taking the green (0) or the blue (1) entry.
And redraw the DOM so buttons like Add to cart
will be disabled when inStock
return false.
<div id="app">
<div class="product">
<div class="product-info">
<div
v-for="(variant, index) in variants"
:key="variant.variantId"
class="color-box"
:style="{ backgroundColor: variant.variantColor }"
@mouseover="updateProduct(index)"
></div>
</div>
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
selectedVariant:0,
variants: [
{
variantId: 2234,
variantColor: 'green',
variantQuantity: 100
},
{
variantId: 2235,
variantColor: 'blue',
variantQuantity: 0
}
],
cart: 0
},
methods: {
updateProduct(index) {
this.selectedVariant = index;
}
},
computed: {
inStock() {
return this.variants[this.selectedVariant].variantQuantity
}
}
}
</script>
A component is a block of HTML / JS code that can be reuse in another projects.
The declaration is like:
<div id="app"><product></product></div>
<script>
Vue.component('product' {
template: `<h1>I'm a product component</h1>`
})
var app = new Vue({
el: '#app'
});
</script>
The Vue component should be placed inside the Vue root element (which is #app
here above); not outside.
Using property (without any validation):
<div id="app"><product productname="Socks"></product></div>
<script>
Vue.component("product", {
props: ["productname"],
template: `<h1>I'm an amazing {{ productname }} product.</h1>`
});
var app = new Vue({
el: "#app"
});
</script>
Using property (with built-in validation):
<div id="app"><product name="Socks"></product></div>
<script>
Vue.component("product", {
props: {
name: {
type: String,
required: true,
default: "AnyProductName"
},
template: `<h1>I'm an amazing {{ name }} product.</h1>`
});
var app = new Vue({
el: "#app"
});
</script>
A Vue instance can be easily converted to a component. Almost everything can be migrated to a component without any changes.
In the previous test (#7), we had data, methods and computed elements. And, of course, in our HTML, we had our DOM elements (the image, the product name, detailed information's, colors and buttons for adding to / removing from cart).
var app = new Vue({
el: "#app",
data: {
[...]
},
methods: {
[...]
},
computed: {
[...]
}
});
data
should become a function (because we should be able to override data when using the component in our final code), methods
and computed
remains unchanged:
Vue.component("product", {
template: `[ OUR HTML ]`,
data() {
return {
[...]
}
},
methods: {
[...]
},
computed: {
[...]
}
});
The final code will be (lighter version):
<div id="app"><product></product></div>
<script src="https://unpkg.com/vue@2"></script>
<script>
Vue.component("product", {
template: `
<div class="product">
<div class="product-image">
<img v-bind:src="image" width="400px"/>
</div>
<div class="product-info">
<h2>{{ titleProduct }}</h2>
<button v-on:click="addToCart">Add to Cart</button>
</div>
</div>
`,
data() {
return {
brand: "Master banch",
product: "Socks",
image: "assets/images/Socks-green.png",
cart: 0
};
},
methods: {
addToCart() {
this.cart += 1;
}
},
computed: {
titleProduct() {
return this.brand + " " + this.product;
}
}
});
var app = new Vue({
el: "#app"
});
</script>
A component will emit an event to inform the parent (the DOM that includes the component) that something has arrived like clicking on the Add to cart
button.
For instance, in our Product component, we already have a addToCart()
method. Instead of using the this.cart += 1;
code, we can put the cart outside the component (so every products will use only one global cart). So this.cart += 1;
can't work anymore if we remove cart
from the data()
function of the component.
The component below has his own cart:
<script>
Vue.component("product", {
[...]
data() {
return {
cart: 0
}
},
methods: {
addToCart() {
this.cart += 1;
}
}
});
var app = new Vue({
el: "#app"
});
</script>
Will be refactored by removing the cart
data and emitting an event:
<script>
Vue.component("product", {
[...]
data() {
return {
}
},
methods: {
addToCart() {
this.$emit('add-to-cart')
}
}
});
var app = new Vue({
el: "#app"
});
</script>
Listening the event is just like any other events:
<div id="app">
<div class="cart"><p>Cart({{cart}})</p></div>
<product @add-to-cart="addCart"></product>
</div>
<script>
Vue.component("product", {
template: `[...]`,
data() {
return {
[...]
}
},
methods: {
addToCart() {
this.$emit('add-to-cart');
},
removeFromCart() {
this.$emit('remove-from-cart');
}
}
});
var app = new Vue({
el: '#app',
data: {
cart: 0
},
methods: {
updateCart() {
this.cart += 1;
},
removeCart() {
if (this.cart > 0) this.cart -= 1;
}
},
});
</script>
For allowing a two-ways binding (not only getting a value but also setting it), we need to use v-model
instead of v-bind
.
The edit box below will be initialized to data.name
but will also update the value when the user will type something in the form.
<input id="name" v-model="name" />
To typecast the value as a number:
<select id="rating" v-model.number="rating"></select>
https://laracasts.com/series/learn-vue-2-step-by-step/episodes/25
v-model
does two things: assign a value to the form's element (like the v-bind
does)
<input type="text" :value="Firstname"/>>
but also update the Vue.js variable i.e. when typing a new value, Vue.js will update the variable.
So
<input type="text" v-model="Firstname" />
can be rewritten like
<input type="text" :value="Firstname" @input="Firstname = $event.target.value" />
($event.target.value
, in pure javascript, returns the value of the object where an event has been triggered).
This long syntax is useful when creating Vue component to keep updated the data in the parent.
The code below will build a review form with four entries: the username, does he/she will recommend or not the product, a rating and his/her review.
@submit.prevent
: attach our code to the submit event of the form and also prevent the normal feature of the browser i.e. refresh the page after a form's submission;v-if="errors.length"
: ouronSubmit
function will check if fields are all filled in and if not, will add errors messages ('Please fill in ...') into globalerrors
array. So the syntaxv-if
will check if the array is empty or not and if not, will display each errors (this part can be migrated into a component);v-model.number="rating"
: the.number
suffix will typecast the data as a number;
Vue.component("product-review", {
template: `
<form class="review-form" @submit.prevent="onSubmit">
<p v-if="errors.length">
<b>Please correct the following error(s):</b>
<ul>
<li v-for="error in errors">{{ error }}</li>
</ul>
</p>
<p>
<label for="name">Name:</label>
<input id="name" v-model="name"/>
</p>
<p>
<label for="review">Review:</label>
<textarea id="review" v-model="review"></textarea>
</p>
<p>
<label for="rating">Rating:</label>
<select id="rating" v-model.number="rating">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</p>
<p>Would you recommend this product?</p>
<label>Yes<input type="radio" value="Yes" v-model="recommend"/></label>
<label>No<input type="radio" value="No" v-model="recommend"/></label>
<p>
<input type="submit" value="Submit"/>
</p>
</form>
`,
data() {
return {
name: null,
review: null,
rating: null,
recommend: null,
errors: []
};
},
methods: {
onSubmit() {
this.errors = [];
if (this.name && this.review && this.rating && this.recommend) {
let ProductReview = {
name: this.name,
review: this.review,
rating: this.rating,
recommend: this.recommend
};
this.$emit("review-submitted", ProductReview);
this.name = null;
this.review = null;
this.rating = null;
this.recommend = null;
} else {
if (!this.name) this.errors.push("Name required.");
if (!this.review) this.errors.push("Review required.");
if (!this.rating) this.errors.push("Rating required.");
if (!this.recommend) this.errors.push("Recommendation required.");
}
}
}
});
The onSubmit()
method here above will create a new ProductReview
object only when the four fields are filled in and will the emit an event review-submitted
with the object as parameter so the parent can receive posted information's and display them f.i.
Tutorial: Tabs Also look Component Communication #2 on Laracast.
Quickly, we'll have a master component lets say product
that will contains child components: product-detail
, product-reviews-tabs
, ... and child components will also have child components. For instance product-review-tabs
can contain a component for displaying reviews and one for showing a form to add a review.
So: Product --> Product-review-tabs --> Product-review-add-form
By submitting a product review, the grandfather needs to receive the event a review has been submitted
. To allow this, a bus event
will be instantiated. This is just like any other Vue instance...
var eventBus = new Vue();
By submitting the review, an event will be triggered:
eventBus.$emit("review-submitted", ProductReview);
And the grandfather will listen this event by using a new property called mounted
:
Vue.component('product', {
props: {
[ ... ]
},
template: `[ ... ]`,
data() {
return {
[ ... ],
reviews: []
}
},
methods: {
[ ... ]
},
computed: {
[ ... ]
},
mounted() {
eventBus.$on('review-submitted', productReview => {
this.reviews.push(productReview)
})
}
});
With a simple tasks list like this:
data: {
tasks: [
{ description: "Go to the store", completed: true },
{ description: "Finish screencast", completed: false },
{ description: "Make donation", completed: false },
{ description: "Clear inbox", completed: false },
{ description: "Make dinner", completed: false },
{ description: "Clean room", completed: true }
];
}
It's easy to display completed tasks and the ones that should still be done:
<div id="app">
<h2>Don't forget</h2>
<ul>
<li
v-for="task in tasks"
v-if="!task.completed"
v-text="task.description"
></li>
</ul>
<h2>Already completed</h2>
<ul>
<li
class="completed"
v-for="task in tasks"
v-if="task.completed"
v-text="task.description"
></li>
</ul>
</div>
With v-if
it's easy to put a condition before doing the display.
Optimization
By the use of a computed variable, the content of the variable will be put in cache so, on the next call and until the real data is changed, the list won't be filtered again and again.
data: {
tasks: [
{ description: "Go to the store", completed: true },
{ description: "Finish screencast", completed: false },
{ description: "Make donation", completed: false },
{ description: "Clear inbox", completed: false },
{ description: "Make dinner", completed: false },
{ description: "Clean room", completed: true }
];
},
computed: {
incompleteTasks() {
return this.tasks.filter(task => !task.completed);
}
}
HTML becomes:
<div id="app">
<h2>Don't forget</h2>
<ul>
<li v-for="task in incompleteTasks" v-text="task.description"></li>
</ul>
<h2>Already completed</h2>
<ul>
<li
class="completed"
v-for="task in completeTasks"
v-text="task.description"
></li>
</ul>
</div>
The HTML below will display a list of tasks.
<div id="app" class="container">
<task-list></task-list>
</div>
task-list
is a view component that will use another component task
. For each task, a card will be displayed with the title and a dummy description but, very easy, will include a close button.
<button type="button" @click="isVisible = false" class="delete"></button>
The expression will just put the isVisible
variable to false. No need to call a jQuery $('#div').close();
or anything else. Just by setting the variable to false will do the trick since the card is upon a v-if
condition: <article class="message is-info" v-show="isVisible">
. Easy!
Vue.component("task-list", {
template: `
<div>
<task v-for="task in tasks">{{ task.description }}</task>
</div>`,
data() {
return {
tasks: [
{ description: "Go to the store", completed: true },
{ description: "Finish screencast", completed: false },
{ description: "Make donation", completed: false },
{ description: "Clear inbox", completed: false },
{ description: "Make dinner", completed: false },
{ description: "Clean room", completed: true }
]
};
}
});
Vue.component("task", {
props: ["title"],
template: `<article class="message is-info" v-show="isVisible">
<div class="message-header">
<p><slot></slot></p>
<button type="button" @click="hideModal"></button>
</div>
<div class="message-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</div>
</article>`,
data() {
return {
isVisible: true
};
}
});
var app = new Vue({
el: "#app",
data: {}
});
CONTINUER https://laracasts.com/series/learn-vue-2-step-by-step/episodes/10
Using Bulma CSS framework for displaying a list of todos.
A task (todo) is a component in Vue with a close button. Clicking on the button will just set a boolean isVisible
variable to false. And that variable is used in the v-show
attribute to show or hide the task.
Vue.component("task", {
props: ["title"],
template: `<article class="message is-info" v-show="isVisible">
<div class="message-header">
<p><slot></slot></p>
<button type="button" @click="isVisible = false" class="delete"></button>
</div>
<div class="message-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</div>
</article>`,
data() {
return {
isVisible: true
};
}
});
In HTML, we'll show a modal
component using a v-if
to show the modal only when a boolean is set to true. The modal contains a button that will emit a close
event. That event will be catched in HTML with the @close
attribute and we'll just set the variable to false so the modal will be hidden.
<div id="app" class="container">
<modal v-if="showModal" @close="showModal = false">Hello world</modal>
<button type="button" @click="showModal = true">Show modal</button>
</div>
Vue.component("modal", {
template: `
<div class="modal is-active">
<div class="modal-background"></div>
<div class="modal-content">
<div class="box">
<slot></slot>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$emit('close')"></button>
</div>`
});
When showing a component (like a modal form), we need to be able to specify the title to use for the modal but also the content and a footer with buttons so not only one single information but several.
Until now, we've used <slot></slot>
for taking everything that was mentioned within the component tag like <modal><h1>This is my slot</h1></slot>
.
In the example below, we'll use a component called modal
with three slots: two named slots (header
and footer
) and one unnamed (aka default slot
).
<modal>
<template slot="header"
>This is my header slot</template
>
Lorem ispo dolor sit amet.
<div slot="footer">
<a class="button is-primary">Save change</a>
<a class="button">Cancel</a>
</div>
</modal>
The component can be something like this:
Vue.component("modal", {
template: `
<div class="modal is-active">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">
<slot name="header"></slot>
</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<slot></slot>
</section>
<footer class="modal-card-foot">
<slot name="footer">
Default footer...
</slot>
</footer>
</div>
</div>`
});
With <slot name="header"></slot>
, we'll use the slot called header
.
With <slot name="footer">
, we'll use the slot called footer
. In this example, we'll specify a default footer. This means that if the parent doesn't provide a footer slot, we'll provide one by default.
The unnamed slot will be used by using <slot></slot>
.
A Vue component always have an associate template like in:
Vue.component("modal", {
template: `
<div class="modal is-active">
...
</div>`
});
But sometimes, it's not needed to put the template there but is more useful to keep it inside our HTML file. For instance, when the need for a component is not for reusability but just to take advantage of the reactivity of Vue.
In order to use an inline template, we just need to add the inline-template
attribute like below:
<progress-review inline-template>
<div>
<h1>Your completion rate is {{ completionRate}}%</h1>
<p><button @click="completionRate += 10">Increase it by 10</button></p>
</div>
</progress-review>
(remember: only one root element inside a template; this is why we need a div
here above)
Vue.component("progress-review", {
data() {
return {
completionRate: 25
};
}
});
A watcher will allow to capture any change to a variable like here below:
var app = new Vue({
el: "#app",
data() {
return {
title: "Nice title"
};
},
watch: {
title: function(val, oldVal) {
console.log("new title: %s, old value: %s", val, oldVal);
}
}
});
- We'll need to install
Vue cli
:npm install -g vue-cli
- Create a new application:
cd c:\repository
(where my-app is the desired application name).vue init webpack-simple my-app
(where my-app is the desired application name).
A new folder c:\repository\my-app
will then be created with a template application.
The file /src/App.vue
will contains a structure with HTML (in the template
section), JS (script
) and CSS (style
). Everything in only one single file, a .vue
file.
<template> </template>
<script></script>
<style></style>
The browser doesn't understand how to use such .vue
file: Webpack will bundle the file into files that can be understand by any browser.
The configuration is stored in the webpack.config.js
file.
The entry point of the application is mentioned in the ´entry` node.
module.exports = {
entry: './src/main.js',
[...]
output
is used for specifying where to put files when we'll compile our application:
module.exports = {
[...]
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
[...]
Webpack will also define various loaders:
module.exports = {
[...]
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
],
}, {
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
}
// other vue-loader options go here
}
},
[...]
npm install
By firing the following instruction, the node environment will be set to development
, webpack-dev-server will be instantiated and a browser automatically fired for this URL: http://localhost:8081/
npm run dev
run dev
is a script defined in the package.json
file, in the scripts
node:
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
}
--hot
means hot reload
: by changing a source file and just save it, the navigator will automatically refresh the page and the changes will be reflected immediately.
In /src/App.vue
, we can f.i. update the content like below i.e. using a custom component called message
:
<template>
<div id="app">
<message>Hello there</message>
<message>Hello World</message>
<message>Hello Universe</message>
</div>
</template>
<script>
import Message from "./components/Message.vue";
export default {
name: "app",
components: { Message },
data() {
return {};
}
};
</script>
<style></style>
That component is just a .vue
file stored in a sub-folder components
.
The Message.vue
file will define a template that will show a div
and our message; with a small styling.
<template>
<div class="box">
<p>
<slot></slot>
</p>
</div>
</template>
<<script>
export default {};
</script>
<<style>
.box {
background: #e3e3e3;
padding: 10px;
border: 1px solid black;
margin-bottom: 10px;
}
</style>
Thanks the hot reload, just saving the files will make the changes immediately visible in the browser tab.
SPA = Single Page Application
French video about how to implement Vue Router. By Grafikart.fr
https://www.grafikart.fr/tutoriels/vue-router-822 - https://www.youtube.com/watch?v=PCsDcWlWeEY&list=PLjwdMgw5TTLW-mAtlR46VajrKs4dep3y0&index=16&t=0s