Categories
Vue 3

Vue 3 — List Transitions

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at creating list transitions.

List Move Transitions

We can add list move transitions.

For instance, we can use the transition-group component to display some effect when we the items change position:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
    <style>
      .list-move {
        transition: transform 0.8s ease;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <button @click="shuffle">shuffle</button>
      <transition-group name="list" tag="div">
        <p v-for="item in items" :key="item">
          {{ item }}
        </p>
      </transition-group>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            items: Array(10)
              .fill()
              .map(() => Math.random()),
          };
        },
        methods: {
          shuffle() {
            this.items = _.shuffle(this.items);
          }
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

We added the list-move class with our transition effect to display it when the list is shuffled.

The list is shuffled with the Lodash shuffle method.

Staggering List Transitions

We can stagger transitions in a list.

To do that, we use the Greensock library to help us.

We can write;

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <input v-model="query" />
      <transition-group
        name="fade"
        tag="div"
        :css="false"
        @before-enter="beforeEnter"
        @enter="enter"
        @leave="leave"
      >
        <p
          v-for="(item, index) in computedList"
          :key="item.name"
          :data-index="index"
        >
          {{ item.name }}
        </p>
      </transition-group>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            query: "",
            list: [
              { name: "james" },
              { name: "mary" },
              { name: "alex" },
              { name: "bob" },
              { name: "jane" }
            ]
          };
        },
        computed: {
          computedList() {
            return this.list.filter(item => {
              return item.name.toLowerCase().includes(this.query.toLowerCase());
            });
          }
        },
        methods: {
          beforeEnter(el) {
            el.style.opacity = 0;
            el.style.height = 0;
          },
          enter(el, done) {
            gsap.to(el, {
              opacity: 1,
              height: "1.3em",
              delay: el.dataset.index * 0.55,
              onComplete: done
            });
          },
          leave(el, done) {
            gsap.to(el, {
              opacity: 0,
              height: 0,
              delay: el.dataset.index * 0.45,
              onComplete: done
            });
          }
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

We included the Greensock library with our app.

In the methods property, we have a few methods.

The beforeEnter method sets the container’s opacity and height to 0.

The enter method has our enter animation effect.

We change the opacity to 1 to make opaque.

height is th height of the container.

delay is the delay of the animation.

onComplete is a function we call to notify Vue that the animation is done.

We do the same thing with the leave transition.

When computedList returns a new value, the animation effects will be applied.

Therefore, when we type in something into the input box, we’ll see the effects applied.

Reusable Transitions

We can make our transitions reusable by moving it into our own component.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <input v-model="query" />
      <list-transition>
        <p
          v-for="(item, index) in computedList"
          :key="item.name"
          :data-index="index"
        >
          {{ item.name }}
        </p>
      </list-transition>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            query: "",
            list: [
              { name: "james" },
              { name: "mary" },
              { name: "alex" },
              { name: "bob" },
              { name: "jane" }
            ]
          };
        },
        computed: {
          computedList() {
            return this.list.filter(item => {
              return item.name.toLowerCase().includes(this.query.toLowerCase());
            });
          }
        }
      }); 

      app.component("list-transition", {
        template: `
        <transition-group
          name="fade"
          tag="div"
          :css="false"
          @before-enter="beforeEnter"
          @enter="enter"
          @leave="leave"
        >
         <slot></slot>
        </transition-group>
        `,
        methods: {
          beforeEnter(el) {
            el.style.opacity = 0;
            el.style.height = 0;
          },
          enter(el, done) {
            gsap.to(el, {
              opacity: 1,
              height: "1.3em",
              delay: el.dataset.index * 0.55,
              onComplete: done
            });
          },
          leave(el, done) {
            gsap.to(el, {
              opacity: 0,
              height: 0,
              delay: el.dataset.index * 0.45,
              onComplete: done
            });
          }
        }
      }); 

      app.mount("#app");
    </script>
  </body>
</html>

to move our transition component and hooks to its own component.

We just create a component and add a slot in between the transition-group tags to add the slot for our content.

Now we can use the list-transition component everywhere.

Conclusion

We can add our list transitions effects with the transition-group component.

It takes various directives to let us add hooks to create JavaScript animations.

Categories
Vue 3

Vue 3 — Props Data Flow

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to use props with Vue 3.

One-Way Data Flow

Props have a one way downward binding between the parent and child component.

When the parent property updates, then the updates are passed into the child via props.

This prevents child components from accidentally mutating the parent’s state.

And this makes our app easier to understand.

We should never mutate props.

If we need to change their value, then we should assign them to a new property first.

For instance, if we need to change the value of an initial value that’s set with the prop’s value, then we should assign that to a state first.

So we should write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <counter :initial-count="5"></counter>
    </div>
    <script>
      const app = Vue.createApp({}); app.component("counter", {
        props: ["initialCount"],
        data() {
          return {
            count: this.initialCount
          };
        },
        template: `
          <div>
            <button @click='count++'>increment</button>
            <p>{{count}}</p>
          </div>
        `
      }); app.mount("#app");
    </script>
  </body>
</html>

We have the initialCount prop that we use to set the initial value of count state in the counter component.

Then we can do whatever we like with it.

If the value needs to be transformed, then we can put it in as a computed property.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <counter :initial-count="5"></counter>
    </div>
    <script>
      const app = Vue.createApp({}); 
      app.component("counter", {
        props: ["initialCount"],
        data() {
          return {
            count: this.initialCount
          };
        },
        computed: {
          doubleCount() {
            return this.count * 2;
          }
        },
        template: `
          <div>
            <button @click='count++'>increment</button>
            <p>{{doubleCount}}</p>
          </div>
        `
      }); app.mount("#app");
    </script>
  </body>
</html>

We have the initial-count prop which is transformed to doubleCount by returning this.count * 2 .

Now we don’t have to do anything with the prop value itself.

And we just change the state to what we want within the data method and the computed property in the counter component.

Prop Validation

We can validate props by check its data type and more.

We set the the props property’s value to a constructor.

Or we can validate it with a function.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post v-for="post of posts"></blog-post>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            posts: [{ author: "james", likes: 100 }]
          };
        }
      });
      app.component("blog-post", {
        props: {
          title: {
            type: String,
            default: "default title"
          }
        },
        template: `<p>{{title}}</p>`
      });
      app.mount("#app");
    </script>
  </body>
</html>

Then we get the ‘default title’ text displayed since we never passed in value to the title prop.

default has the default value.

validator has the validator function for props.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post v-for="post of posts" type="news"></blog-post>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            posts: [{ author: "james", likes: 100 }]
          };
        }
      });
      app.component("blog-post", {
        props: {
          type: {
            validator(value) {
              return ["news", "announcement"].indexOf(value) !== -1;
            }
          }
        },
        template: `<p>{{type}}</p>`
      });
      app.mount("#app");
    </script>
  </body>
</html>

to add a validator for the type prop.

The validator method is run when we pass in the prop with the given name.

The value is the value that we pass in.

So if we pass in something other than 'new' or 'announcement' like:

<blog-post v-for="post of posts" type="foo"></blog-post>

then we’ll get a warning.

We can also add the required property and set it to true to make a prop required like:

prop: {
  type: String,
  required: true
}

Conclusion

We can validate props with constructors and validation functions.

Also, we can add the default property to set the default value of a prop.

Categories
Vue 3

Vue 3 — More Complex Props

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to use props with Vue 3.

Passing a Number

We can pass in numbers to props.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post v-for="post of posts" :likes="post.likes"></blog-post>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            posts: [{ title: "hello world", author: "james", likes: 100 }]
          };
        }
      }); 
      app.component("blog-post", {
        props: {
          likes: Number
        },
        template: `<p>{{likes}}</p>`
      }); 
      app.mount("#app");
    </script>
  </body>
</html>

to create the blog-post component that takes the likes prop.

likes is a number, so we’ve to put the : before the likes to let us pass in an expression.

We can also pass in a number literal:

<blog-post :likes="100"></blog-post>

Passing a Boolean

To pass in a boolean, we can write:

<blog-post is-published></blog-post>

to pass in true to the is-published prop.

To pass in false , we’ve write out the whole expression:

<blog-post :is-published='false'></blog-post>

And we can pass in other expressions.

Passing an Array

We can pass in an array literal as a prop value.

So we can write:

<blog-post :comment-ids="[1, 2, 3]"></blog-post>

or we can write:

<blog-post :comment-ids="post.commentIds"></blog-post>

Passing an Object

Vue props can take objects.

So we can write:

<blog-post
  :author="{
    firstName: 'james',
    lastName: 'smith'
  }"
></blog-post>

We pass in an object to the author prop.

The : tells Vue that the object isn’t a string.

We can also pass in another expression that returns an object.

Passing the Properties of an Object

To pass in the properties of an object as props, we can use the v-bind without the argument.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post v-for="post of posts" v-bind="post"></blog-post>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            posts: [{ title: "hello world", author: "james", likes: 100 }]
          };
        }
      }); 
      app.component("blog-post", {
        props: {
          title: String,
          author: String,
          likes: Number
        },
        template: `
          <div>
            <h1>{{title}}</h1>
            <p>author: {{author}}</p>
            <p>likes: {{likes}}</p>
          </div>
        `
      }); 
      app.mount("#app");
    </script>
  </body>
</html>

Then the properties of post will be passed into blog-post as prop values.

The property names are the prop names.

This is a shorthand for:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post
        v-for="post of posts"
        :title="post.title"
        :author="post.author"
        :likes="post.likes"
      ></blog-post>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            posts: [{ title: "hello world", author: "james", likes: 100 }]
          };
        }
      }); 
      app.component("blog-post", {
        props: {
          title: String,
          author: String,
          likes: Number
        },
        template: `
          <div>
            <h1>{{title}}</h1>
            <p>author: {{author}}</p>
            <p>likes: {{likes}}</p>
          </div>
        `
      }); 
      app.mount("#app");
    </script>
  </body>
</html>

As we can see, it’s much longer and a lot more repetitive than the shorthand.

Conclusion

We can pass in various kinds of data easily with Vue components.

We just need to use v-bind or : for short.

Categories
Vue 3

Vue 3 — Component Events

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to listen to component events.

Listening to Child Components Events

We can listen to child components within the parent component.

Our child component has to emit the event so that the parent component can listen to it.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <div :style="{ 'font-size': `${fontSize}px` }">
        <todo-item
          @enlarge-text="fontSize++"
          v-for="t of todos"
          :todo="t"
          :key="t.id"
        ></todo-item>
      </div>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            fontSize: 15,
            todos: [
              { id: 1, name: "eat" },
              { id: 2, name: "drink" },
              { id: 3, name: "sleep" }
            ]
          };
        }
      }); 
      app.component("todo-item", {
        props: ["todo"],
        template: `
          <div>
            <p>{{ todo.name }}</p>
            <button @click="$emit('enlarge-text')">
              Enlarge text
            </button>
        </div>
        `
      }); 
      app.mount("#app");
    </script>
  </body>
</html>

We created a todo-item component with an ‘enlarge text’ button.

When we click it, the $emit function is run.

The argument is the event name.

Then in the parent component that holds the todo-item component, we have the @enlarge-text directive so that we can listen to the enlarge-text event that’s emitted from todo-item .

When that event is received, when we increase fontSize by 1.

Since we set fontSize as the font size of our div, then font size change will be applied across all the child elements.

Emitting a Value With an Event

The $emit function takes a second argument with the value we want to emit with the event.

Therefore, we can modify our code to pass the value into the $emit function.

Then we get the value emitted from the $event object.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <div :style="{ 'font-size': `${fontSize}px` }">
        <todo-item
          @enlarge-text="fontSize += $event"
          v-for="t of todos"
          :todo="t"
          :key="t.id"
        ></todo-item>
      </div>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            fontSize: 15,
            todos: [
              { id: 1, name: "eat" },
              { id: 2, name: "drink" },
              { id: 3, name: "sleep" }
            ]
          };
        }
      }); 

      app.component("todo-item", {
        props: ["todo"],
        template: `
          <div>
            <p>{{ todo.name }}</p>
            <button @click="$emit('enlarge-text', 1)">
              Enlarge text
            </button>
          </div>
        `
      }); 
      app.mount("#app");
    </script>
  </body>
</html>

$emit takes a second argument that we emit with the enlarge-text event.

Now in the parent component, we get the value emitted with the $event variable.

In this case, $event is set to 1 since that’s what we emitted.

We changed the @enlarge-text ‘s value so that the fontSize updates from the $event instead of a constant value.

Also, we can put the expression we passed into @enlarge-text into a method.

This is handy if we have more code.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="[https://unpkg.com/vue@next](https://unpkg.com/vue@next)"></script>
  </head>
  <body>
    <div id="app">
      <div :style="{ 'font-size': `${fontSize}px` }">
        <todo-item
          @enlarge-text="onEnlargeText"
          v-for="t of todos"
          :todo="t"
          :key="t.id"
        ></todo-item>
      </div>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            fontSize: 15,
            todos: [
              { id: 1, name: "eat" },
              { id: 2, name: "drink" },
              { id: 3, name: "sleep" }
            ]
          };
        },
        methods: {
          onEnlargeText(amount) {
            this.fontSize += amount;
          }
        }
      }); 

      app.component("todo-item", {
        props: ["todo"],
        template: `
          <div>
            <p>{{ todo.name }}</p>
            <button @click="$emit('enlarge-text', 1)">
              Enlarge text
            </button>
        </div>
        `
      }); 
      app.mount("#app");
    </script>
  </body>
</html>

We changed @enlarge-text ‘s value to our onEnlargeText method.

amount would be automatically set to the $event variable’s value, which is 1.

So we get the same result.

Conclusion

We can listen to the child component’s events from the parent.

This way, we can pass data from child to parent.

Categories
Vue 3

Vue 3 — Component Basics

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to create simple Vue 3 components.

Components

Components are reusable Vue instances with a name.

For instance, we can create a component by writing:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <button-counter ></button-counter>
    </div>
    <script>
      const app = Vue.createApp({}); 
      app.component("button-counter", {
        data() {
          return {
            count: 0
          };
        },
        template: `
          <div>
            <button @click="count--">
              decrement
            </button>
            <p>{{ count }}</p>
          </div>
        `
      }); 
      app.mount("#app");
    </script>
  </body>
</html>

We create a component with a button and a p element.

When we click the button, the count is decrement, and the value is displayed in the p element.

Then we used the button-counter element in the app’s template.

We can reuse the component multiple times.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <button-counter></button-counter>
      <button-counter></button-counter>
      <button-counter></button-counter>
    </div>
    <script>
      const app = Vue.createApp({}); 
      app.component("button-counter", {
        data() {
          return {
            count: 0
          };
        },
        template: `
          <div>
            <button @click="count--">
              decrement
            </button>
            <p>{{ count }}</p>
          </div>
        `
      }); 
      app.mount("#app");
    </script>
  </body>
</html>

Now we have 3 instances of the button-counter component.

They all have their own template and state.

Organizing Components

We got to organize our components well since we’ll probably have many of them in an app.

We can put them in a tree.

So we divide our app into small components we nest components to build what we want.

Passing Data to Child Components with Props

Our previous example didn’t have any way to get data from a parent component.

However, Vue 3 provides us with a way to pass data from parent to child with props.

Props are custom attributes that we can register on a component.

When a value is passed into the prop attribute, it becomes a property of the component instance.

For instance, we can create a component that takes props and use it by writing:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post title="hello world"></blog-post>
    </div>
    <script>
      const app = Vue.createApp({}); 
      app.component("blog-post", {
        props: ["title"],
        template: `<h1>{{ title }}</h1>`
      }); 
      app.mount("#app");
    </script>
  </body>
</html>

We create the blog-post component with the title prop so that we can display it on the template.

The props property has an array of strings of the prop names.

Having that means we registered that props to be allowed to be passed into our component.

A component can have as many props as we want.

If we have an array of data, we can use the v-for directive to render them.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <todo-item v-for="t of todos" :todo="t" :key="t.id"></todo-item>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            todos: [
              { id: 1, name: "eat" },
              { id: 2, name: "drink" },
              { id: 3, name: "sleep" }
            ]
          };
        }
      }); 

      app.component("todo-item", {
        props: ["todo"],
        template: `<p>{{ todo.name }}</p>`
      }); 
      app.mount("#app");
    </script>
  </body>
</html>

to render an array with components.

We have the todo-item component with the todo prop registered.

The template property rendered the name property of the array.

todo-item is used with v-for to render the todo items.

We pass in the todo value with the todo prop with :todo='t' and do the same with the key prop.

The key prop is used to let Vue identify each element uniquely.

Conclusion

We can create our own components with the app.component method so that we can divide our Vue app into manageable pieces.