Categories
Vue

Build a Multi-Step Form in a Vue App with the vue-stepper Package

To build multi-step forms easily in a Vue app, we can use the vue-stepper package to help us.

In this article, we’ll look at how to use the vue-stepper package to add a multi-step form into our app.

Installation

To install the library, we run:

npm i vue-stepper

Create a Multistep Form

To create a multistep form, we need a component for the container.

Also, we need a component to display the step content.

For example, we can write:

App.vue

<template>
  <div id="app">
    <horizontal-stepper
      :steps="demoSteps"
      @completed-step="completeStep"
      @active-step="isStepActive"
      @stepper-finished="alert"
    ></horizontal-stepper>
  </div>
</template>

<script>
import HorizontalStepper from "vue-stepper";
import StepOne from "./components/StepOne";
import StepTwo from "./components/StepTwo";

export default {
  name: "App",
  components: {
    HorizontalStepper
  },
  data() {
    return {
      demoSteps: [
        {
          icon: "mail",
          name: "first",
          title: "Step 1",
          subtitle: "Step 1",
          component: StepOne,
          completed: false
        },
        {
          icon: "report_problem",
          name: "second",
          title: "Step 2",
          subtitle: "Step 2",
          component: StepTwo,
          completed: false
        }
      ]
    };
  },
  methods: {
    completeStep(payload) {
      this.demoSteps.forEach(step => {
        if (step.name === payload.name) {
          step.completed = true;
        }
      });
    },
    isStepActive(payload) {
      this.demoSteps.forEach(step => {
        if (step.name === payload.name) {
          if (step.completed === true) {
            step.completed = false;
          }
        }
      });
    },
    alert(payload) {
      alert("end");
    }
  }
};
</script>

StepOne.vue

<template>
  <div>
    <input placeholder="email" v-model="email" @input="onChange">
  </div>
</template>

<script>
export default {
  data() {
    return {
      email: ""
    };
  },
  beforeMount(){
    if (this.email){
      this.$emit("can-continue", { value: true });
    }
  },
  methods: {
    onChange() {
      this.$emit("can-continue", { value: true });
    }
  }
};
</script>

StepTwo.vue

<template>
  <div>
    <input placeholder="problem" v-model="problem" @input="onChange">
  </div>
</template>

<script>
export default {
  data() {
    return {
      problem: ""
    };
  },
    beforeMount(){
    if (this.problem){
      this.$emit("can-continue", { value: true });
    }
  },
  methods: {
    onChange() {
      this.$emit("can-continue", { value: true });
    }
  }
};
</script>

In App.vue , we have the container for the steps.

The template has the horizontal-stepper component that has the icon, title, and subtitle of the form.

The completed-step event is emitted when we click next.

active-step is emitted when we go to a different step.

stepper-finished is emitted when all the steps are finished.

StepOne.vue and StepTwo.vue are our step components.

We set them as the content with the component property in the demoSteps array.

When we type some into the inputs, then the input event is emitted.

The onChange method is run when the input method is emitted so the can-continue event is emitted. When it’s emitted with an object with the value property set to true , then the Next button is enabled.

Now we can continue with the other steps.

Once we click the Finish button, then the stepper-finished event is emitted and we can see an alert because the alert method is run.

Conclusion

We can add a multi-step form into our Vue app with the vue-stepper package.

Categories
Modern JavaScript

Best of Modern JavaScript — Symbols API

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at JavaScript symbols.

Crossing Realms with Symbols

Symbols don’t travel well across realms.

Since there’re different global objects in different frames, we can’t use them across frames.

Special symbols like Symbol.iterator should work across different realms.

To get a symbol across different realms, we can use the Symbol.for method to get the symbol with a string.

For example, we can write:

const sym = Symbol.for('foo');

Then we can call Symbol.keyFor to get the string we passed into Symbol to create the symbol:

Symbol.keyFor(sym)

Special symbols like Symbol.iterator will return undefined if we call it with Symbol.keyFor :

Symbol.keyFor(Symbol.iterator)

Are Symbols Primitives or Objects?

Symbols are primitive values.

Symbols are like strings in that they can be used as property keys.

But they’re like objects in that each symbol has their own identity.

They’re immutable and they can be used as property keys.

However, it doesn’t have many properties of objects like prototypes and wrappers.

Symbol also can’t be examined by operators and methods like instance or Object.keys .

Why are Symbols Useful?

Symbols are useful for avoid clashes of identifiers.

This is easy since we can’t create the same symbol twice.

Are JavaScript Symbols Like Ruby’s Symbols?

JavaScript and Ruby symbols aren’t alike.

Ruby symbols are literals for creating values.

For instance, if we have:

`:foo` `==` `:foo`

then the expression returns true .

Symbol(‘foo’) !== Symbol(‘foo’) returns true .

So no 2 symbols are the same even though we passed in the same argument.

The Spelling of Well-Known Symbols

Well-known symbols are spelled this way because they’re used as normal property keys.

Symbol API

Symbol is a function that takes a string as its description.

So we can use it by writing:

const sym = Symbol('foo');

to get a new symbol.

Methods of Symbols

The only useful method in a symbol is the toString method.

Well-known Symbols

There’re several well-knowns that may be useful to us.

The Symbol.hasInstance method lets an object customize the behavior of the instanceof operator.

Symbol.toPrimitive is a method that lets us customize how it’s converted to a primitive value.

This is the 1st steep whenever something is being coerced into a primitive value.

Symbol.toStringTag is a method called by Object.prototype.toString to return the string description of an object.

Therefore, we can override it to provide our own string description.

Symbol.unscopables is a method that hides some properties with the with statement.

Symbol.iterator is a method that lets us define our own iterable to create an iterable object.

Once we added this method, we can iterate it with the for-of operator.

String Methods

String methods are forwarded to methods with the given symbols.

They’re forwarded as follows:

  • String.prototype.match(str, ...) is forwarded to str[Symbol.match]().
  • String.prototype.replace(str, ...) is forwarded to str[Symbol.replace]().
  • String.prototype.search(str, ...) is forwarded to str[Symbol.search](···).
  • String.prototype.split(str, ...) is forwarded to str[Symbol.split]().

Other Special Symbols

Symbol.species is a method to configure built-in methods to create objects similar to this .

Symbol.isConcatSpreadable is a boolean to configure whether Array.prototype.concat adds the indexed element as the result.

Conclusion

There’re many special symbols we can use to override the behavior of objects.

Categories
Vue

Make HTTP Requests in a Vue App with Fetch

The Fetch API is an HTTP client that is built into modern browsers.

Therefore, we can use it to make HTTP requests without installing anything.

In this article, we’ll look at how to make requests with the Fetch API in a Vue app.

Making Requests

We can make requests with the fetch function.

For example, we can write:

<template>
  <div id="app"></div>
</template>
<script>
export default {
  name: "App",
  async beforeMount() {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts/1", {
      method: "GET",
      mode: "cors"
    });
    const data = await res.json();
    console.log(data);
  }
};
</script>

to make a simple GET request with fetch .

The first argument is the URL.

And the 2nd is an object with the method and mode properties.

The method property is the request method.

The mode is set to 'cors' to let us make cross-origin requests.

To make a POST request, we can rite:

<template>
  <div id="app"></div>
</template>
<script>
export default {
  name: "App",
  async beforeMount() {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        title: "foo",
        body: "bar",
        userId: 1
      })
    });
    const data = await res.json();
    console.log(data);
  }
};
</script>

We set the headers property to set the request headers.

The body property has the stringified JSON object used for the request body.

Uploading a File

To upload a file, we can write:

<template>
  <div id="app">
    <input type="file" @change="onChange">
  </div>
</template>
<script>
export default {
  name: "App",
  methods: {
    async onChange(ev) {
      const formData = new FormData();
      formData.append("username", "abc123");
      formData.append("avatar", ev.target.files[0]);
      const res = await fetch(
        "https://run.mocky.io/v3/c5189845-2a93-49aa-85c7-70bc64e8af90 ",
        {
          method: "PUT",
          body: formData
        }
      );
      console.log(res);
    }
  }
};
</script>

We have a file input and a change event listener to listen for file selection changes.

The onChange method is the change listener.

And in it, we create a FormData instance and add the file to it with the ev.target.files[0] file object.

Then we make the request by setting the body property to the formData object.

For example, we can write:

<template>
  <div id="app">
    <input type="file" @change="onChange" multiple>
  </div>
</template>
<script>
export default {
  name: "App",
  methods: {
    async onChange(ev) {
      const formData = new FormData();
      formData.append("username", "abc123");
      for (const file of ev.target.files) {
        formData.append("photos", file);
      }
      const res = await fetch(
        "https://run.mocky.io/v3/c5189845-2a93-49aa-85c7-70bc64e8af90 ",
        {
          method: "PUT",
          body: formData
        }
      );
      console.log(res);
    }
  }
};
</script>

We loop through the ev.target.files object with the for-of loop to add all the files from the object to the request body.

Check if a Request is Successful

We can check if a request is successful by catching the error.

Only network errors are raised. This doesn’t include completed requests with 400 or 500 series error codes.

For example, we can write:

<template>
  <div id="app"></div>
</template>
<script>
export default {
  name: "App",
  async beforeMount() {
    try {
      const res = await fetch("https://picsum.photos/200");
      const data = await res.blob();
      console.log(data);
    } catch (error) {
      console.error(error);
    }
  }
};
</script>

to catch the error with the catch block.

Conclusion

We can make HTTP requests in a Vue app with the Fetch API.

Categories
Vue

Make HTTP Requests in a Vue App with vue-resource — More Requests

The vue-resource library is a useful HTTP client library for Vue apps.

It’s a good alternative to Axios for making requests.

In this article, we’ll look at how to use it to make HTTP requests with a Vue app.

Setting Request Headers and Query Parameters Per Request

We can set headers per request by setting the headers property of the request object.

To set the query string parameters, we can put them in the object we set as the value of the params property.

For example, we can write:

<template>
  <div id="app">{{response}}</div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      response: {}
    };
  },
  async beforeMount() {
    const { bodyText } = await this.$http.get("https://api.agify.io", {
      params: { name: "michael" },
      headers: { Accept: "application/json" }
    });
    this.response = JSON.parse(bodyText);
  }
};
</script>

Resource

We can use the this.$resource property to make different requests with the same base URL.

For example, we can write:

<template>
  <div id="app">{{response}}</div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      response: {}
    };
  },
  async beforeMount() {
    const resource = this.$resource(
      "https://jsonplaceholder.typicode.com/posts{/id}"
    );
    const getResponse = await resource.get({ id: 1 });
    console.log(getResponse);

    const saveResponse = await resource.save(
      {},
      { title: "foo", body: "bar", userId: 1 }
    );
    console.log(saveResponse);
  }
};
</script>

We called the this.$resource method with the base URL to return a resource object that we can call.

{/id} is a placeholder for the URL parameter.

Then we can call resource.get to make a GET request.

And resource.save makes a POST request. The first argument is an object with the URL parameter values.

The 2nd argument is the request body.

The query method also makes a GET request.

The update method makes a PUT request.

And the delete or remove method makes a DELETE request.

For example, we can call query by writing:

<template>
  <div id="app">{{response}}</div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      response: {}
    };
  },
  async beforeMount() {
    const resource = this.$resource(
      "https://jsonplaceholder.typicode.com/posts{/id}"
    );
    const getResponse = await resource.query({ id: 1 });
    console.log(getResponse);
  }
};
</script>

To make a PUT request, we can write:

<template>
  <div id="app">{{response}}</div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      response: {}
    };
  },
  async beforeMount() {
    const resource = this.$resource(
      "https://jsonplaceholder.typicode.com/posts{/id}"
    );
    const updateResponse = await resource.update(
      { id: 1 },
      {
        title: "foo",
        body: "bar",
        userId: 1
      }
    );
    console.log(updateResponse);
  }
};
</script>

The argument is the same as the other methods.

To make a DELETE request, we can write:

<template>
  <div id="app">{{response}}</div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      response: {}
    };
  },
  async beforeMount() {
    const resource = this.$resource(
      "https://jsonplaceholder.typicode.com/posts{/id}"
    );
    const removeResponse = await resource.remove({ id: 1 });
    console.log(removeResponse);
  }
};
</script>

or:

<template>
  <div id="app">{{response}}</div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      response: {}
    };
  },
  async beforeMount() {
    const resource = this.$resource(
      "https://jsonplaceholder.typicode.com/posts{/id}"
    );
    const removeResponse = await resource.delete({ id: 1 });
    console.log(removeResponse);
  }
};
</script>

Conclusion

We can make requests easily in a Vue app with the vue-resource library.

Categories
Vue

Make HTTP Requests in a Vue App with vue-resource

The vue-resource library is a useful HTTP client library for Vue apps.

It’s a good alternative to Axios for making requests.

In this article, we’ll look at how to use it to make HTTP requests with a Vue app.

Installation

We can install the library by running:

yarn add vue-resource

or

npm install vue-resource

It can also be included with a script tag:

<script src="https://cdn.jsdelivr.net/npm/vue-resource@1.5.1"></script>

Making Requests

To make a request, we register the plugin in main.js by writing:

import Vue from "vue";
import App from "./App.vue";
import VueResource from "vue-resource";

Vue.use(VueResource);
Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App)
}).$mount("#app");

Then to make the request, we write:

<template>
  <div id="app">{{response}}</div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      response: {}
    };
  },
  async beforeMount() {
    const { bodyText } = await this.$http.get(
      "https://api.agify.io//?name=michael"
    );
    this.response = JSON.parse(bodyText);
  }
};
</script>

We call this.$http.get to make a get request to an endpoint.

To make a post request, we can write:

<template>
  <div id="app">{{response}}</div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      response: {}
    };
  },
  async beforeMount() {
    const { bodyText } = await this.$http.post(
      "https://jsonplaceholder.typicode.com/posts",
      {
        title: "foo",
        body: "bar",
        userId: 1
      }
    );
    this.response = JSON.parse(bodyText);
  }
};
</script>

We just pass the request body into the 2nd argument.

Getting Response

We can get the response header from the headers property:

"<template>
  <div id="app">{{response}}</div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      response: {}
    };
  },
  async beforeMount() {
    const response = await this.$http.post(
      "https://jsonplaceholder.typicode.com/posts",
      {
        title: "foo",
        body: "bar",
        userId: 1
      }
    );
    console.log(response.headers.get("Expires"));
    this.response = JSON.parse(response.bodyText);
  }
};
</script>

We pass the header key to the get method to get its value.

Getting a Blob

We can get a blob with the this.$http.get method to get a blob.

All we have to do is set the responseType to 'blob' :

"<template>
  <div id="app"></div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      response: {}
    };
  },
  async beforeMount() {
    const response = await this.$http.get(
      "https://picsum.photos/id/23/200/300",
      { responseType: "blob" }
    );
    const blob = await response.blob();
    console.log(blob);
  }
};
</script>

Interceptors

We add an interceptor to set the request header for all requests.

We can set the request headers globally by writing:

main.js

import Vue from "vue";
import App from "./App.vue";
import VueResource from "vue-resource";

Vue.use(VueResource);
Vue.config.productionTip = false;
Vue.http.interceptors.push((request) => {
  request.headers.set("X-CSRF-TOKEN", "TOKEN");
  request.headers.set("Authorization", "Bearer TOKEN");
});

new Vue({
  render: (h) => h(App)
}).$mount("#app");

App.vue

<template>
  <div id="app">{{response}}</div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      response: {}
    };
  },
  async beforeMount() {
    const { bodyText } = await this.$http.post(
      "https://jsonplaceholder.typicode.com/posts",
      {
        title: "foo",
        body: "bar",
        userId: 1
      }
    );
    this.response = JSON.parse(bodyText);
  }
};
</script>

We have the Vue.http.interceptors.push method to modify requests with the request.headers.set method.

Conclusion

We can use the vue-resource library to make requests in a Vue app.