Categories
Vue

Add Authentication to a Vue App with vue-authenticate

Adding authentication to an app is always a chore.

If we’re building a Vue app, we can make our work easier with the vue-authenticate plugin.

In this article, we’ll look at how to use the vue-authenticate plugin to add authentication.

Getting Started

We can get started by installing a few packages.

We run:

npm install vue-authenticate axios vue-axios

to install the required packages.

Vue-authenticate uses Axios and Vue-Axios for making HTTP requests.

Then we can add the plugin to our app by writing:

import Vue from "vue";
import App from "./App.vue";
import VueAxios from "vue-axios";
import VueAuthenticate from "vue-authenticate";
import axios from "axios";

Vue.use(VueAxios, axios);
Vue.use(VueAuthenticate, {
  baseUrl: "https://ClosedThirdPixels--five-nine.repl.co"
});
Vue.config.productionTip = false;

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

in main.js .

We set the baseUrl to the base URL of the API.

Then we can create a login form with it by writing:

<template>
  <div>
    <form @submit.prevent="login">
      <input v-model="email" type="text" placeholder="email">
      <input v-model="password" type="password" placeholder="password">
      <br>
      <input type="submit" value="log in">
    </form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      email: "",
      password: ""
    };
  },
  methods: {
    async login() {
      const { email, password } = this;
      await this.$auth.login({ email, password });
    }
  }
};
</script>

in a component.

We use the this.$auth.login method to make a POST request to https://closedthirdpixels–five-nine.repl.co/auth/login.

The payload has the email and password fields.

On the back end, we have:

const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors')

const app = express();
app.use(cors())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.post('/auth/login', (req, res) => {
  res.json({})
});

app.listen(3000, () => console.log('server started'));

to handle the request.

We add the cors middleware so that we can make cross-domain requests.

We can add more code to handle login credential validation.

To add a user registration form, we can write:

<template>
  <div>
    <form @submit.prevent="register">
      <input v-model="name" type="text" placeholder="name">
      <input v-model="email" type="text" placeholder="email">
      <input v-model="password" type="password" placeholder="password">
      <br>
      <input type="submit" value="log in">
    </form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      name: "",
      email: "",
      password: ""
    };
  },
  methods: {
    async register() {
      const { name, email, password } = this;
      await this.$auth.register({ name, email, password });
    }
  }
};
</script>

to create a form.

And when we submit the form, we call the register method, which calls the this.$auth.register method to make a POST request to https://closedthirdpixels–five-nine.repl.co/auth/register.

Everything in the arguments will be sent.

Then we can add a route to the back end to handle that request:

const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors')

const app = express();
app.use(cors())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.post('/auth/login', (req, res) => {
  res.json({})
});

app.post('/auth/register', (req, res) => {
  res.json({})
});

app.listen(3000, () => console.log('server started'));

Conclusion

We can add basic authentication to a Vue app with the Vue-Authenticate library.

Categories
Vue

Vue Select — Pagination, Infinite Scrolling, and Disabling Selections

To make dropdowns easier, we can use the Vue Select plugin to add the dropdown.

It can do much more than the select element.

In this article, we’ll look at how to use the vue-select package to make more complex dropdowns.

Selectable Prop

We can set which options are selected able with the selectable prop.

For example, we can write:

<template>
  <div id="app">
    <v-select
      placeholder="Choose fruit"
      :options="options"
      :selectable="option => option !== 'grape'"
    ></v-select>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      options: ["apple", "orange", "grape"]
    };
  }
};
</script>

We set the selectable prop to a function that returns the condition for the items that we want to disable.

Therefore, we disable the 'grape' choice in the dropdown.

The placeholder has the placeholder for the dropdown.

Limiting the Number of Selections

We can limit the number of selections with the selectable prop.

For example, we can write:

<template>
  <div id="app">
    <v-select
      placeholder="Choose fruit"
      :options="options"
      multiple
      v-model="selected"
      :selectable="() => selected.length < 3"
    ></v-select>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      selected: [],
      options: ["apple", "orange", "grape", "banana", "pear"]
    };
  }
};
</script>

The selectable prop is set to a function that checks the selected array’s length.

If we have more than 3, then the choices will be disabled.

Pagination

We can paginate the choices in the dropdown.

For example, we can write:

<template>
  <v-select :options="paginated" @search="query => search = query" :filterable="false">
    <li slot="list-footer" class="pagination">
      <button @click="offset -= 10" :disabled="!hasPrevPage">Prev</button>
      <button @click="offset += 10" :disabled="!hasNextPage">Next</button>
    </li>
  </v-select>
</template>

<script>
import countries from "./countries";
export default {
  data: () => ({
    countries,
    search: "",
    offset: 0,
    limit: 10
  }),
  computed: {
    filtered() {
      return this.countries.filter(country => country.includes(this.search));
    },
    paginated() {
      return this.filtered.slice(this.offset, this.limit + this.offset);
    },
    hasNextPage() {
      const nextOffset = this.offset + 10;
      return Boolean(
        this.filtered.slice(nextOffset, this.limit + nextOffset).length
      );
    },
    hasPrevPage() {
      const prevOffset = this.offset - 10;
      return Boolean(
        this.filtered.slice(prevOffset, this.limit + prevOffset).length
      );
    }
  }
};
</script>

The countries array is from the countryList in https://gist.github.com/incredimike/1469814.

We populate the list-footer slot with buttons to let us move through the pages.

Then we create the paginated computed property to get the items on a given page.

Also, we have the hasNextPage method to check if there’s the next page by seeing if there’s anything returned by slice .

And we use a similar logic with the hasPrevPage computed property.

The search event is emitted when we enter something in the search box.

Infinite Scrolling

We can also add infinite scrolling to our v-select dropdown.

For example, we can write:

<template>
  <v-select
    :options="paginated"
    :filterable="false"
    @open="onOpen"
    @close="onClose"
    @search="query => search = query"
  >
    <template #list-footer>
      <li ref="load" class="loader" v-show="hasNextPage">Loading more options...</li>
    </template>
  </v-select>
</template>

<script>
import countries from "./countries";

export default {
  data: () => ({
    observer: null,
    limit: 10,
    search: ""
  }),
  mounted() {
    this.observer = new IntersectionObserver(this.infiniteScroll);
  },
  computed: {
    filtered() {
      return countries.filter(country => country.includes(this.search));
    },
    paginated() {
      return this.filtered.slice(0, this.limit);
    },
    hasNextPage() {
      return this.paginated.length < this.filtered.length;
    }
  },
  methods: {
    async onOpen() {
      if (this.hasNextPage) {
        await this.$nextTick();
        this.observer.observe(this.$refs.load);
      }
    },
    onClose() {
      this.observer.disconnect();
    },
    async infiniteScroll([{ isIntersecting, target }]) {
      if (isIntersecting) {
        const ul = target.offsetParent;
        const scrollTop = target.offsetParent.scrollTop;
        this.limit += 10;
        await this.$nextTick();
        ul.scrollTop = scrollTop;
      }
    }
  }
};
</script>

We use the Intersection Observer API to check if we scrolled to the bottom of the list.

We start observing after the onOpen method is called when we open the dropdown.

Then we stop observing when the onClose method is called to stop observing.

The this.observer property is created from the IntersectionObserver constructor with the infiniteScroll method.

We can get whether we scrolled to the bottom of the dropdown list with the isIntersecting property.

Conclusion

We can add pagination and infinite scrolling with the dropdown with the Vue Select dropdown.

Also, we can disable and limit selections that we can choose.

Categories
Vue

Vue Select — Loading Options and Loops

To make dropdowns easier, we can use the Vue Select plugin to add the dropdown.

It can do much more than the select element.

In this article, we’ll look at how to use the vue-select package to make more complex dropdowns.

Loading Options with Ajax

We can load options with Ajax.

For example, we can write:

<template>
  <v-select @search="fetchOptions" :options="options"/>
</template>

<script>
export default {
  data() {
    return {
      allOptions: ["apple", "orange", "grape"],
      options: []
    };
  },
  methods: {
    fetchOptions(search, loading) {
      setTimeout(() => {
        this.options = this.allOptions.filter(a =>
          a.toLowerCase().includes(search.toLowerCase())
        );
      }, 1000);
    }
  }
};
</script>

to add the fetchOptions method and set it as the listener of the search event.

The search parameter has our search query.

loading lets us toggle the loading state.

Then we set the options prop to the options state.

Disabling Filtering

We can disable client-side filtering when we’re loading the options from the server-side.

To do that, we set the filterable prop to false :

<template>
  <v-select @search="fetchOptions" :options="options" :filterable="false"/>
</template>

<script>
export default {
  data() {
    return {
      allOptions: ["apple", "orange", "grape"],
      options: []
    };
  },
  methods: {
    fetchOptions(search, loading) {
      setTimeout(() => {
        this.options = this.allOptions.filter(a =>
          a.toLowerCase().includes(search.toLowerCase())
        );
      }, 1000);
    }
  }
};
</script>

Loading Spinner

We can add a spinner by populating the spinner slot:

<template>
  <v-select @search="fetchOptions" :options="options" :filterable="false">
    <template v-slot:spinner="{ loading }">
      <div v-show="loading">Loading...</div>
    </template>
  </v-select>
</template>

<script>
export default {
  data() {
    return {
      allOptions: ["apple", "orange", "grape"],
      options: []
    };
  },
  methods: {
    fetchOptions(search, loading) {
      loading(true);
      setTimeout(() => {
        this.options = this.allOptions.filter(a =>
          a.toLowerCase().includes(search.toLowerCase())
        );
        loading(false);
      }, 2000);
    }
  }
};
</script>

loading is a function that pass a boolean value to set the loading class.

If we pass true , then the loading class is added.

Otherwise, it’s removed.

We put whatever we want to display as the loading indicator in the spinner slot.

Now when we search for something, the Loading… text will be displayed.

Vue Select in v-for Loops

We can use Vue Select within loops.

For example, we can write:

<template>
  <table>
    <tr>
      <th>Name</th>
      <th>Country</th>
    </tr>
    <tr v-for="p in people" :key="p.name">
      <td>{{ p.name }}</td>
      <td>
        <v-select
          :options="options"
          :value="p.country"
          @input="country => updateCountry(p, country)"
        />
      </td>
    </tr>
  </table>
</template>

<script>
export default {
  data: () => ({
    people: [{ name: "John", country: "" }, { name: "Jane", country: "" }],
    options: ["Canada", "United States"]
  }),
  methods: {
    updateCountry(person, country) {
      person.country = country;
    }
  }
};
</script>

We created a table with rows created with v-for .

The v-select component has the dropdowns to add.

We set the dropdown choice for the entry by listening to the input event.

It’ll be run when we select a choice with the dropdown.

Conclusion

We can load data from the server-side and show a loading indicator with the Vue-Select component.

The dropdowns can also be used in loops.

Categories
Vue

Vue Select — Custom Search and Positioning

To make dropdowns easier, we can use the Vue Select plugin to add the dropdown.

It can do much more than the select element.

In this article, we’ll look at how to use the vue-select package to make more complex dropdowns.

Customizing Keydown Behaviour

We can customize keydown behavior to do what we want.

For example, we can write:

<template>
  <div>
    <v-select no-drop taggable multiple :select-on-key-codes="[188, 13]"/>
  </div>
</template>

<script>
export default {
  data: () => ({})
};
</script>

to add the selections when we press the comma key in addition to the return key.

188 is the keycode for the comma key and 13 is the return key.

We can append what we want to the selection after a selection is made.

For example, we can write:

<template>
  <v-select taggable multiple no-drop :map-keydown="handlers" placeholder="enter an email"/>
</template>

<script>
export default {
  methods: {
    handlers: (map, vm) => ({
      ...map,
      50: e => {
        e.preventDefault();
        if (e.key === "@" && vm.search.length > 0) {
          vm.search = `${vm.search}@hotmail.com`;
        }
      }
    })
  }
};
</script>

We append the @hotmail.com suffix to our selection once we type the @ sign and we typed in something to the input box before that.

Popper.js Integration

Vue Select integrates with Popper.js.

For example, we can write:

<template>
  <div style="margin-top: 6em">
    <v-select :options="countries" append-to-body :calculate-position="withPopper"/>
  </div>
</template>

<script>
import { createPopper } from "@popperjs/core";

export default {
  data: () => ({ countries: ["Canada", "United States"], placement: "top" }),
  methods: {
    withPopper(dropdownList, component, { width }) {
      dropdownList.style.width = width;
      const popper = createPopper(component.$refs.toggle, dropdownList, {
        placement: this.placement,
        modifiers: [
          {
            name: "offset",
            options: {
              offset: [0, -1]
            }
          },
          {
            name: "toggleClass",
            enabled: true,
            phase: "write",
            fn({ state }) {
              component.$el.classList.toggle(
                "drop-up",
                state.placement === "top"
              );
            }
          }
        ]
      });
      return () => popper.destroy();
    }
  }
};
</script>

We added the withPopper method to position the dropdown with the modifiers property to let us change the position.

If state.placement is 'top' , then we set the dropdown to drop up instead.

The this.placement variable sets the placement of the dropdown.

If we set the placement to 'bottom' , then we get the dropdown to display at the bottom as usual.

Filtering with Fuse.js

Fuse.js makes filtering the dropdown entries easier for us.

For example, we can write:

<template>
  <v-select :filter="fuseSearch" :options="books" :getOptionLabel="option => option.title">
    <template #option="{ author, title }">
      {{ title }}
      <br>
      <cite>{{ author.firstName }} {{ author.lastName }}</cite>
    </template>
  </v-select>
</template>

<script>
import Fuse from "fuse.js";

export default {
  data() {
    return {
      books: [
        {
          title: "book",
          author: {
            firstName: "james",
            lastName: "smith"
          }
        },
        {
          title: "book 2",
          author: {
            firstName: "jones",
            lastName: "smith"
          }
        },
        {
          title: "javascript for dummies",
          author: {
            firstName: "may",
            lastName: "wong"
          }
        }
      ]
    };
  },
  methods: {
    fuseSearch(options, search) {
      const fuse = new Fuse(options, {
        keys: ["title", "author.firstName", "author.lastName"],
        shouldSort: true
      });
      return search.length
        ? fuse.search(search).map(({ item }) => item)
        : fuse.list;
    }
  }
};
</script>

to add the fuseSearch method to let us do the search.

The method has the options array with all the options.

The search parameter is our search query.

The keys have the properties that we want to search by.

shouldSort lets us set whether we sort the items or not.

We then return the results by using the fuse.search method to do the search.

It’ll search all the properties automatically since we specified them in the keys array.

If the query is empty,. then we return the whole list with fuse.list .

Conclusion

We can customize various behaviors with the Vue Select dropdown.

The dropdown position can be changed, and it integrates with various libraries.

Categories
Vue

Vue Select — Complex Dropdowns and Validation

To make dropdowns easier, we can use the Vue Select plugin to add the dropdown.

It can do much more than the select element.

In this article, we’ll look at how to use the vue-select package to make more complex dropdowns.

Single/Multiple Selection

We can enable multiple selection the v-select component with the multiple prop:

<template>
  <div id="app">
    <v-select multiple v-model="selected" :options="['Canada','United States']"/>
    {{selected}}
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      selected: ""
    };
  }
};
</script>

Now we can type in the results and select more than one choice.

Tagging

We can add the taggable prop to add choices that aren’t present in the dropdown.

For example, we can write:

<template>
  <div id="app">
    <v-select taggable multiple v-model="selected" :options="['Canada','United States']"/>
    {{selected}}
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      selected: ""
    };
  }
};
</script>

Now we can type in anything and it’ll be selected.

Also, we can add the push-tags prop to push the option to the options array:

<template>
  <div id="app">
    <v-select taggable push-tags multiple v-model="selected" :options="options"/>
    <div>{{selected}}</div>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      selected: "",
      options: ["Canada", "United States"]
    };
  }
};
</script>

Now when we type in something, we’ll see it in the dropdown.

Even though it’s in the dropdown, it’s not added to the options array.

Using taggable & reduce Together

We can use the taggable and reduce props together if we add the create-option prop.

For example, we can write:

<template>
  <div id="app">
    <v-select
      taggable
      multiple
      label="title"
      :options="options"
      v-model="selected"
      :create-option="book => ({ title: book, author: { firstName: '', lastName: '' } })"
      :reduce="book => `${book.author.firstName} ${book.author.lastName}`"
    />
    <div>{{selected}}</div>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      selected: undefined,
      options: [
        {
          title: "HTML for dummies",
          author: {
            firstName: "james",
            lastName: "smith"
          }
        },
        {
          title: "JavaScript for dummies",
          author: {
            firstName: "mary",
            lastName: "smith"
          }
        }
      ]
    };
  }
};
</script>

We have the create-option prop to return the object that we want to add to the dropdown as additional choices.

The reduce prop has a function that determines what’s added to the selected array.

Validation

v-select supports validation.

For example, we can add:

<template>
  <div id="app">
    <v-select :options="options" label="title" v-model="selected">
      <template #search="{attributes, events}">
        <input :required="!selected" v-bind="attributes" v-on="events">
      </template>
    </v-select>
    <div>{{selected}}</div>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      selected: undefined,
      options: ["Canada", "United States"]
    };
  }
};
</script>

to add the required prop to the input for searching for items.

We put our customs search box in the sesrch slot.

And we pass all the attributes and events to the input element.

Conclusion

We can enable multiple selection and add validation to a dropdown with the Vue Select package.