Let’s create the form that adds a new product.

The AddProduct component is the piece of the app responsible for showing the form and intercepting the form submit event.

We now have this:

<template>
  <div class="">
    <h1>Add Product</h1>
  </div>
</template>

We want to have a form where we can enter the product name, the product description, the price and a link to a picture (not uploads now, as we don’t want to get in o the backend part).

First, we define the markup of the form:

<template>
  <form>
    <h1>Add Product</h1>
    <div>
    <label>Name:</label> <input required v-model="name">
    </div>
    <div>
    <label>Price in $:</label><input required v-model="price">
    </div>
    <div><label>Description:</label> <textarea required v-model="description"></textarea>
    </div>
    <div>
    <label>Image URL:</label> <input required v-model="imageUrl">
    </div>
    <input type="submit" value="Add" class="button">
  </form>
</template>

We are mapping the

  • name
  • price
  • description
  • imageUrl

properties to the component data:

<script>
export default {
  name: 'AddProduct',
  data() {
    return {
      name: '',
      price: 0,
      description: '',
      imageUrl: ''
    }
  }
}
</script>

Now we want to intercept the form submit event, and call a method in out component.

We do so by hooking the addProduct method to the submit event: <form @submit="addProduct">

We also add the prevent event modifier to make sure the form is not sent as a normal HTML form, and we only handle it using JavaScript:

<form @submit.prevent="addProduct">

We add the addProduct event to the component:

<script>
export default {
  //...
  methods: {
    addProduct() {
      //...
    }
  }
}
</script>

Inside it, we interact with the Vuex store, which is accessible through this.$store since we installed Vuex in the main.js file.

We call the addProduct mutation on the store, passing the new product object:

this.$store.commit('addProduct', {
  name: this.name,
  price: this.price,
  description: this.description,
  imageUrl: this.imageUrl
})

Here’s the full code so far:

<template>
  <form @submit.prevent="addProduct">
    <h1>Add Product</h1>
    <div>
    <label>Name:</label> <input required v-model="name">
    </div>
    <div>
    <label>Price in $:</label><input required v-model="price">
    </div>
    <div><label>Description:</label> <textarea required v-model="description"></textarea>
    </div>
    <div>
    <label>Image URL:</label> <input required v-model="imageUrl">
    </div>
    <input type="submit" value="Add" class="button">
  </form>
</template>

<script>
export default {
  name: 'AddProduct',
  data() {
    return {
      name: '',
      price: 0,
      description: '',
      imageUrl: ''
    }
  },
  methods: {
    addProduct() {
      this.$store.commit('addProduct', {
        name: this.name,
        price: this.price,
        description: this.description,
        imageUrl: this.imageUrl
      })
    }
  }
}
</script>

Here’s the form:

Now we add a little bit of CSS to style the form, and make it look better than now:

<style scoped>
input,
textarea {
  border: 1px solid #ccc;
  padding: 20px;
  vertical-align: middle;
}

label {
  vertical-align: middle;
  padding-right: 10px;
}

div {
  display: block;
  padding: 50px;font-size: 1.5rem;
}

.button {
  background-color: #2c3e50;
  color: white;
  font-size: 1.5rem;
  width: 50%;
}
</style>

Nothing fancy, some CSS rules to make the form look better:

The form now shows up nicely, but it still does nothing. We need to add the addProduct mutation to the Vuex store.

Here’s the src/store.js file at the moment:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {}
})

We want to store the list of products in a products array, so we add it:

export default new Vuex.Store({
  state: {
    products: []
  },
  mutations: {},
  actions: {}
})

Now we’re ready to add the addProduct mutation, which adds the product to the state.products array:

export default new Vuex.Store({
  state: {
    products: [],
  },
  mutations: {
    addProduct(state, product) {
      state.products.push(product)
    }
  },
  actions: {}
})

Now things work as we expect! You can inspect the Vuex store state through the Vue developer tools, which conveniently has a Vuex tab out of the box:

After adding a product, here’s what the DevTools show us:

As you can see, in addition to the current state you can inspect the mutations history that caused the current state, and you can navigate back in time by clicking the different mutations, to check how the state evolved. Pretty handy!

Note that we don’t persist the products data anywhere, so every time you refresh the page, the data is lost. We’ll persist this data later, in lesson 7 “Persisting data”.

We must do something else now, however. We need to calculate a product slug, which is part of its URL.

Every product has a URL like /product/product-name. We calculate this slug part when the product is added, based on its name.

Here’s the function that computes the slug:

const slugify = str => {
  str = str || ''
  const a =
    'àáäâèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;άαβγδεέζήηθιίϊΐκλμνξοόπρσςτυϋύΰφχψωώ'
  const b =
    'aaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------aavgdeeziitiiiiklmnxooprsstyyyyfhpoo'
  const p = new RegExp(a.split('').join('|'), 'g')

  return str
    .toString()
    .trim()
    .toLowerCase()
    .replace(/ου/g, 'ou')
    .replace(/ευ/g, 'eu')
    .replace(/θ/g, 'th')
    .replace(/ψ/g, 'ps')
    .replace(/\//g, '-')
    .replace(/\s+/g, '-') // Replace spaces with -
    .replace(p, c => b.charAt(a.indexOf(c))) // Replace special chars
    .replace(/&/g, '-and-') // Replace & with 'and'
    .replace(/[^\w\-]+/g, '') // Remove all non-word chars
    .replace(/\-\-+/g, '-') // Replace multiple - with single -
    .replace(/^-+/, '') // Trim - from start of text
    .replace(/-+$/, '') // Trim - from end of text
}

You don’t need to understand it. I picked it from StackOverflow and it does its job in my tests. It accepts a string like A mug and returns a-mug, but you don’t have to believe me: copy/paste it in the developer tools, and test some strings.

Now we just need to call it in the mutation, before storing the product in the array:

addProduct(state, product) {
  product.slug = slugify(product.name)
  state.products.push(product)
}

The code at this point is available at https://codesandbox.io/s/zzxmm452k3


Go to the next lesson