Skip to content

DonaH/vue3-intro

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

37 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Vue 3 Basic

Simple Product App

Overview of Topics


Resources:


Details for Each Topic

Intro to Vue 3

  • VueMastery Video
  • Covers fundamentals of building a Vue app
  • Administer variations of product
  • Add to cart / remove from cart logic
  • In stock / out of stock logic
  • Build a review form with Vue
  • Recommended extension for VSCode:
    • es6-string-html by Tobermory

Creating the Vue App

  • VueMastery Video

  • Create Vue app:

    πŸ“‚ main.js

    const app = Vue.createApp({
      data() {
        return {
          product: 'Socks'
        }
      `}`
    })
    
  • Mount Vue app:

    πŸ“‚ index.html

    <html>
      <body>
        <div id="app"></div>
    
        <!-- Import App -->
        <script src="./main.js"></script>
    
        <!-- Mount App -->
        <script>const mountedApp = app.mount('#app')</script>
      </body>
    </html>
    
  • Once the app is mounted, you can run JavaScript Expression inside html file such as:

      <h1>{{ product }}</h1>
      <p>{{ firstName + ' ' + lastName }}</p>
      <span>{{ clicked ? true : false }}</span>
      <div>{{ message.method() }}</div>
    

Reactivity System underneath the hood will take care of the DOM updates. Learn more here >> Reactivity in Depth


Attribute Binding

  • VueMastery Video

  • Attribute binding example:

    <img v-bind:src="image">
    <img :src="image"> <!-- equivalent shorthand -->
    
  • More examples:

    <img :src="image">
    <img :alt="description">1
    <a :href="url">
    <div :class="isActive">
    <span :style="isActive">
    <span :disables="isDisabled">
    

Conditional Rendering

  • VueMastery Video

  • Directives: v-if | v-else | v-show

    πŸ“‚ main.js

      const app = Vue.createApp({
        data() {
            return {
                product: 'Socks',
                image: './assets/images/socks_blue.jpg',
                inStock: true 
            }
        }
      })
    

    πŸ“‚ index.html

    <p v-if="inStock">In Stock</p>
    <p v-else>Out of Stock</p>
    
    <p v-show="inStock">In Stock</p>
    

    πŸ“‚ main.js

    const app = Vue.createApp({
        data() {
            return {
                ...
                inventory: 100 
        }
    

    πŸ“‚ index.html

    <p v-if="inventory > 10">In Stock</p>
    <p v-else>Out of Stock</p>
    
    <p v-if="inventory > 10">In Stock</p>
    <p v-else-if="inventory <= 10 && inventory > 0">Almost sold out!</p>
    <p v-else>Out of Stock</p>
    

List Rendering

  • VueMastery Video

  • Looping through an array list and array of objects: v-for

    πŸ“‚ main.js

    const app = Vue.createApp({
        data() {
            return {
                ...
                details: ['50% cotton', '30% wool', '20% polyester']
            }
        }
    })
    

    πŸ“‚ index.html

    <ul>
      <li v-for="detail in details">{{ detail }}</li>
    </ul>
    

    πŸ“‚ main.js

    data() {
      return {
        ...
        variants: [
          { id: 2234, color: 'green' },
          { id: 2235, color: 'blue' }
        ]
      }
    }
    

    πŸ“‚ index.html

    <div v-for="variant in variants" :key="variant.id">{{ variant.color }}</div>
    

Event Handling

  • VueMastery Video

  • Event Handling

    πŸ“‚ index.html

    <div class="cart">Cart({{ cart }})</div>
    ...
    <button class="button">Add to Cart</button>
    

    πŸ“‚ main.js

    data() {
      return {
        cart: 0,
        ...
      }
    }
    

    πŸ“‚ index.html

    <button class="button" v-on:click="run logic here">Add to Cart</button>
    

    Call the method on click:

    <button class="button" v-on:click="addToCart">Add to Cart</button>
    

    πŸ“‚ main.js

    const app = Vue.createApp({
      data() {
        return {
          cart: 0,
          ...
        }
      },
      methods: {
        addToCart() {
          this.cart += 1
        }
      }
    })
    

    Shorthand for 'v-on' is '@'

    <button class="button" @click="addToCart">Add to Cart</button>
    

    πŸ“‚ main.js

    data() {
      return {
        ...
        variants: [
          { id: 2234, color: 'green', image: './assets/images/socks_green.jpg' },
          { id: 2235, color: 'blue', image: './assets/images/socks_blue.jpg' },
        ]
      }
    }
    

    πŸ“‚ index.html

    <div v-for="variant in variants" :key="variant.id" @mouseover="updateImage(variant.image)">{{ variant.color }}</div>
    

    πŸ“‚ main.js

    methods: {
      ...
      updateImage(variantImage) {
        this.image = variantImage
      }
    }
    

Class and Style Binding

  • VueMastery Video

  • Style Binding :open_file_folder: index.html

    <div 
      v-for="variant in variants" 
      :key="variant.id" 
      @mouseover="updateImage(variant.image)" 
      class="color-circle" 
    </div>
    

    πŸ“‚ style.css

    .color-circle {
      width: 50px;
      height: 50px;
      margin-top: 8px;
      border: 2px solid #d8d8d8;
      border-radius: 50%;
    } 
    

    πŸ“‚ index.html

    <div 
      v-for="variant in variants" 
      :key="variant.id" 
      @mouseover="updateImage(variant.image)" 
      class="color-circle" 
      :style="{ backgroundColor: variant.color }">
    </div>
    

    Understanding Style Binding

    <div 
      ...
      :style="{ backgroundColor: variant.color }">
    </div>
    

    Camel vs Kebab

    Use Camel case for css property inside JavaScript Expression:

    <div :style="{ backgroundColor: variant.color }></div>
    

    Add quotes if you want to use Kebab case:

    <div :style="{ 'background-color': variant.color }></div>
    
  • Style Binding: Objects :open_file_folder: index.html

    <div :style="styles"></div>
    

    πŸ“‚ main.js

    data() {
      return {
        styles: {
          color: 'green',
          fontSize: '14px'
        }
      }
    }
    
    
  • Class Binding

    • Disable 'add to cart' when out of stock

    • Make button look disabled

      πŸ“‚ index.html

      <button 
        class="button" 
        :disabled="!inStock" 
        v-on:click="addToCart">
        Add to Cart
      </button>
      

      πŸ“‚ style.css

      .disabledButton {
        background-color: #d8d8d8;
        cursor: not-allowed;
      }
      
    • Conditionally apply the class when out of stock

      πŸ“‚ index.html

      <button 
        class="button" 
        :class="{ disabledButton: !inStock }" 
        :disabled="!inStock" 
        @click="addToCart">
        Add to Cart
      </button>
      
  • Multiple Class Names Conditionally add the active class

    πŸ“‚ index.html

    <div class="color-circle"
      :class="{ active: activeClass }">
    </div>
    

    πŸ“‚ main.js

    data() {
      return {
        activeClass: true
      }
    }
    

    Combined look:

    <div class="color-circle active"></div>
    
  • Ternary Operators

    In-line example:

    <div :class="[ isActive ? activeClass : '' ]"></div>
    

Computed Properties

  • VueMastery Video

  • Simple Computed Property

    πŸ“‚ main.js

    data() {
      return {
        product: 'Socks',
        brand: 'Vue Mastery'
    }
    

    πŸ“‚ index.html

    <h1>{{ brand + ' ' + product }}</h1>
    

    Compute the Brand and Product Together: :open_file_folder: index.html

    <h1>{{ title }}</h1>
    

    πŸ“‚ main.js

    ...
    computed: {
      title() {
        return this.brand + ' ' + this.product
      }
    }
    
  • Computing Image & Quantity Add quantity property to the variant objects

    πŸ“‚ main.js

    data() {
      return {
        ...
        variants: [
          { id: 2234, color: 'green', image: './assets/images/socks_green.jpg', quantity: 50 },
          { id: 2235, color: 'blue', image: './assets/images/socks_blue.jpg', quantity: 0 },
        ]
      }
    }
    

    Replace updateImage() with new method updateVariant() πŸ“‚ index.html

    <div 
      v-for="(variant, index) in variants" 
      :key="variant.id" 
      @mouseover="updateVariant(index)" <! -- new method -->
      class="color-circle" 
      :style="{ backgroundColor: variant.color }">
    </div>
    

    Add new data property which will be updated to equal index πŸ“‚ main.js

    data() {
      return {
        ...
        selectedVariant: 0,
        ...
      }
    }
    

    πŸ“‚ main.js

    updateVariant(index) {
      this.selectedVariant = index
    }
    

    Delete image & inStock from data, replace with computed properties :open_file_folder: main.js

    computed: {
      ...
      image() {
        return this.variants[this.selectedVariant].image
      },
      inStock() {
        return this.variants[this.selectedVariant].quantity
      }
    }
    

Components and Props

Components

  • Create ProductDisplay component :open_file_folder: components/ProductDisplay.js

    app.component('product-display', {})
    
  • Template :open_file_folder: components/ProductDisplay.js

    app.component('product-display', {
      template: 
        /*html*/ 
        `<div class="product-display">
          <div class="product-container">
            <div class="product-image">
              <img v-bind:src="image">
            </div>
            <div class="product-info">
              <h1>{{ title }}</h1>
      
              <p v-if="inStock">In Stock</p>
              <p v-else>Out of Stock</p>
      
              <div 
                v-for="(variant, index) in variants" 
                :key="variant.id" 
                @mouseover="updateVariant(index)" 
                class="color-circle" 
                :style="{ backgroundColor: variant.color }">
              </div>
              
              <button 
                class="button" 
                :class="{ disabledButton: !inStock }" 
                :disabled="!inStock" 
                v-on:click="addToCart">
                Add to Cart
              </button>
            </div>
          </div>
        </div>`
    })
    

    Notice the template literal back ticks ``, /*html*/ & VSCode extension:es6-string-html, syntax highlighting should work in VSCode.

  • Move data & methods from main.js to here

    πŸ“‚ components/ProductDisplay.js

    app.component('product-display', {
      template: 
        /*html*/ 
        `<div class="product-display">
          ...
        </div>`,
      data() {
        return {
            product: 'Socks',
            brand: 'Vue Mastery',
            selectedVariant: 0,
            details: ['50% cotton', '30% wool', '20% polyester'],
            variants: [
              { id: 2234, color: 'green', image: './assets/images/socks_green.jpg', quantity: 50 },
              { id: 2235, color: 'blue', image: './assets/images/socks_blue.jpg', quantity: 0 },
            ]
        }
      },
      methods: {
          addToCart() {
              this.cart += 1
          },
          updateVariant(index) {
              this.selectedVariant = index
          }
      },
      computed: {
          title() {
              return this.brand + ' ' + this.product
          },
          image() {
              return this.variants[this.selectedVariant].image
          },
          inStock() {
              return this.variants[this.selectedVariant].quantity
          }
      }
    })
    
  • Cleaning up main.js - leaving only cart data & empty methods for now

    const app = Vue.createApp({
      data() {
        return {
          cart: 0,
        }
      },
      methods: {}
    })
    
  • Importing the Component

    πŸ“‚ index.html

    <!-- Import Components -->
    <script src="./components/ProductDisplay.js"></script>
    
  • Using it in the template:

    πŸ“‚ index.html

    <div id="app">
      <div class="nav-bar"></div>
    
      <div class="cart">Cart({{ cart }})</div>
      <product-display></product-display>
    </div>
    
  • Example of reusable blocks - add two more product-display components

    πŸ“‚ index.html

    <div id="app">
      <div class="nav-bar"></div>
    
      <div class="cart">Cart({{ cart }})</div>
      <product-display></product-display>
      <product-display></product-display>
      <product-display></product-display>
    </div>
    

Props

  • props are custom attributes for passing data into a component.

Giving component a prop - premium account

  • set the type & if it's required

    πŸ“‚ main.js

    const app = Vue.createApp({
      data() {
        return {
          cart: 0,
          premium: true
        }
      }
    })
    

    πŸ“‚ components/ProductDisplay.js

    app.component('product-display', {
      props: {
        premium: {
          type: Boolean,
          required: true
        }
      },
      ...
    }
    
  • Add custom attribute to product-display component

πŸ“‚ index.html

  <div id="app">
  <div class="nav-bar"></div>

  <div class="cart">Cart({{ cart }})</div>
    <product-display :premium="premium"></product-display>
  </div>
  • Conditionally display shipping cost - if premium is true, shipping is free, otherwise, $2.99

πŸ“‚ components/ProductDisplay.js

  template: 
  /*html*/
  `<div class="product-display">
    ...
      <p>Shipping: {{ shipping }}</p>
    ...
  </div>`,

πŸ“‚ components/ProductDisplay.js

  computed: {
    ...
    shipping() {
      if (this.premium) {
        return 'Free'
      }
        return 2.99
      }
  }

Communicating Events

  • VueMastery Video

  • Emitting the event - from product display component to main.js so we can update the cart

    πŸ“‚ components/ProductDisplay.js

    methods: {
      addToCart() {
        this.$emit('add-to-cart')
      }
      ...
    }
    

    πŸ“‚ index.html

    <product-display :premium="premium" @add-to-cart="updateCart"></product-display>
    
  • Add updateCart() method in main.js

    πŸ“‚ main.js

    const app = Vue.createApp({
      data() {
        return {
          cart: [],
          ...
        }
      },
      methods: {
        updateCart() {
          this.cart += 1
        }
      }
    })
    
  • Add product id to the cart

    πŸ“‚ main.js

    const app = Vue.createApp({
      data() {
        return {
          cart: [],
          ...
        }
      },
      methods: {
        updateCart(id) {
          this.cart.push(id)
        }
      }
    
  • Add a payload to add-to-cart event emission, so updateCart has access to that id

    πŸ“‚ components/ProductDisplay.js

    methods: {
      addToCart() {
        this.$emit('add-to-cart', this.variants[this.selectedVariant].id)
      }
      ...
    }
    
  • Since no need to display the id in the cart, we can use .length method to display the amount of items instead

    <div id="app">
    ...
    <div class="cart">Cart({{ cart.length }})</div>
    ...
    </div>
    

Forms and v-model

  • VueMastery Video

  • Add a Product Review Form with Two-way Binding: v-model

  • Create new component - ReviewForm.js

    πŸ“‚ components/ReviewForm.js

    app.component('review-form', {
    template:
    /*html*/
    `<form class="review-form">
      <h3>Leave a review</h3>
      <label for="name">Name:</label>
      <input id="name">
    
      <label for="review">Review:</label>      
      <textarea id="review"></textarea>
    
      <label for="rating">Rating:</label>
      <select id="rating">
        <option>5</option>
        <option>4</option>
        <option>3</option>
        <option>2</option>
        <option>1</option>
      </select>
    
      <input class="button" type="submit" value="Submit">
    </form>`,
      data() {
        return {
          name: '',
          review: '',
          rating: null
        }
      }
    })
    
  • Inside of the template, notice these elements:

    • <input id="name">
    • <textarea id="review">
    • <select id="rating">
  • Bind these input fields to their respective data properties

      data() {
      return {
        name: '',
        review: '',
        rating: null
      }
    }
    
  • Add v-model directive to each of these input elements

    πŸ“‚ components/ReviewForm.js

    app.component('review-form', {
    template:
    /*html*/
    `<form class="review-form">
      <h3>Leave a review</h3>
      <label for="name">Name:</label>
      <input id="name" v-model="name">
    
      <label for="review">Review:</label>      
      <textarea id="review" v-model="review"></textarea>
    
      <label for="rating">Rating:</label>
      <select id="rating" v-model.number="rating">
        <option>5</option>
        <option>4</option>
        <option>3</option>
        <option>2</option>
        <option>1</option>
      </select>
    
      <input class="button" type="submit" value="Submit">  
      </form>`,
      data() {
        return {
          name: '',
          review: '',
          rating: null
      }
    })
    

    Notice how on the <select> element, we used v-model.number this is a modifier that typecasts the value as a number.

  • Submitting the Review Form

    • Add a listener

      πŸ“‚ components/ReviewForm.js

      app.component('review-form', {
      template:
      /*html*/
      `<form class="review-form" @submit.prevent="onSubmit">
        ...
        <input class="button" type="submit" value="Submit">  
        </form>`
        ...
      })
      
    • Add onSubmit() method

      πŸ“‚ components/ReviewForm.js

      ...
      data() {
        return {
          name: '',
          review: '',
          rating: null
        }
      },
      methods: {
        onSubmit() {
          let productReview = {
            name: this.name,
            review: this.review,
            rating: this.rating,
          }
          this.$emit('review-submitted', productReview)
      
          this.name = ''
          this.review = ''
          this.rating = null
        }
      }
      ...
      
    • Import the Review Form

      πŸ“‚ index.html

      <!-- Import Components -->
      ...
      <script src="./components/ReviewForm.js"></script>
      ...
      
    • Use review-form component inside Product Display

      πŸ“‚ components/ProductDisplay.js

      template: 
      /*html*/
      `<div class="product-display">
        <div class="product-container">
        ...
        </div>
        <review-form></review-form>
        </div>`
      })
      
  • Adding Product Reviews

    • Add event listener onto the review-form

      πŸ“‚ components/ProductDisplay.js

      template: 
      /*html*/
      `<div class="product-display">
        <div class="product-container">
        ...
        </div>
        <review-form @review-submitted="addReview"></review-form>
        </div>`
      })
      
    • Add a new addReview() method

      πŸ“‚ components/ProductDisplay.js

      ...
      data() {
        return {
          ...
          reviews: []
        }
      },
      methods: {
        ...
        addReview(review) {
          this.reviews.push(review)
        }
      },
      ...
      
  • Displaying the reviews

    • Create new component - ReviewList.js to show the product review

      πŸ“‚ components/ReviewList.js

      app.component('review-list', {
      props: {
        reviews: {
          type: Array,
          required: true
        }
      },
      template:
      /*html*/
      `
      <div class="review-container">
      <h3>Reviews:</h3>
        <ul>
          <li v-for="(review, index) in reviews" :key="index">
            {{ review.name }} gave this {{ review.rating }} stars
            <br/>
            "{{ review.review }}"
            <br/>
          </li>
        </ul>
      </div>
      `
      })
      
    • Import this new component

      πŸ“‚ index.html

      <!-- Import Components -->
      ...
      <script src="./components/ReviewList.js"></script>
      ...
      

      πŸ“‚ components/ProductDisplay.js

      template: 
        /*html*/
        `<div class="product-display">
          <div class="product-container">
          ...
          </div>
          <review-list :reviews="reviews"></review-list>
          <review-form @review submitted="addReview"></review-form>
        </div>`
      })
      
  • Don't show the review if none is present using .length method

    πŸ“‚ components/ProductDisplay.js

    template: 
      /*html*/
      `<div class="product-display">
        ...
        <review-list v-if="reviews.length" :reviews="reviews"></review-list>
        ...
      </div>`
    })
    
  • Basic Form Validation

    πŸ“‚ components/ReviewForm.js

    methods: {
      onSubmit() {
        if (this.name === '' || this.review === '' || this.rating === null) {
          alert('Review is incomplete. Please fill out every field.')
          return
        }
      ...
      }
    }
    

  • Last Modified On: 2021-08-09
  • Creation Date: 2021-08-05

About

A Vue 3 Introduction per VueMastery Course.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published