Categories
React

Add a Calendar to a React App with react-big-calendar

With the react-big-calendar library, we can add a calendar to a React app.

In this article, we’ll look at how to add a calendar with it.

Getting Started

We can install it by running:

npm install --save react-big-calendar

or:

yarn add react-big-calendar

Then we can use it by writing:

import React from "react";
import { Calendar, momentLocalizer } from "react-big-calendar";
import moment from "moment";
import "react-big-calendar/lib/css/react-big-calendar.css";

const localizer = momentLocalizer(moment);

const myEventsList = [
  { start: new Date(), end: new Date(), title: "special event" }
];

export default function App() {
  return (
    <div className="App">
      <Calendar
        localizer={localizer}
        events={myEventsList}
        startAccessor="start"
        endAccessor="end"
        style={{ height: 500 }}
      />
    </div>
  );
}

We add the moment library to provide localization with the momentLocalizer function.

events has an array of events.

startAccessor is the property for the start date of events.

endAccessor is the property for the end date of events.

We can also use date-fns for localization.

To do that, we write:

import React from "react";
import { Calendar, dateFnsLocalizer } from "react-big-calendar";
import format from "date-fns/format";
import parse from "date-fns/parse";
import startOfWeek from "date-fns/startOfWeek";
import getDay from "date-fns/getDay";
import "react-big-calendar/lib/css/react-big-calendar.css";

const locales = {
  "en-US": require("date-fns/locale/en-US")
};

const localizer = dateFnsLocalizer({
  format,
  parse,
  startOfWeek,
  getDay,
  locales
});

const myEventsList = [
  { start: new Date(), end: new Date(), title: "special event" }
];

export default function App() {
  return (
    <div className="App">
      <Calendar
        localizer={localizer}
        events={myEventsList}
        startAccessor="start"
        endAccessor="end"
        style={{ height: 500 }}
      />
    </div>
  );
}

We only change how the localizer is created.

The localizer determines how dates are formatted.

With both examples, we should see a calendar with some events displayed.

format is a function to format dates.

parse is a function to parse dates.

startOfWeek is the day value for the start of the week for a given locale.

getDay is a function to get the day from a date.

locales is an array of locales.

Custom Styling

We can also add SASS styles provided by the library to style our calendar.

We can add them by writing:

@import 'react-big-calendar/lib/sass/styles';
@import 'react-big-calendar/lib/addons/dragAndDrop/styles';

We need the 2nd line if we’re using drag and drop.

Drag and Drop

We can make calendar events draggable by using the withDragAndDrop higher order component to create a calendar component that we can drag events with.

For example, we can write:

import React from "react";
import { Calendar, momentLocalizer } from "react-big-calendar";
import moment from "moment";
import withDragAndDrop from "react-big-calendar/lib/addons/dragAndDrop";
import "react-big-calendar/lib/addons/dragAndDrop/styles.css";
import "react-big-calendar/lib/css/react-big-calendar.css";

const localizer = momentLocalizer(moment);

const events = [{ start: new Date(), end: new Date(), title: "special event" }];

const DnDCalendar = withDragAndDrop(Calendar);

class App extends React.Component {
  state = {
    events
  };

onEventResize = (data) => {
    const { start, end } = data;

    this.setState((state) => {
      state.events[0].start = start;
      state.events[0].end = end;
      return { events: state.events };
    });
  };

  onEventDrop = (data) => {
    console.log(data);
  };

  render() {
    return (
      <div className="App">
        <DnDCalendar
          defaultDate={moment().toDate()}
          defaultView="month"
          events={this.state.events}
          localizer={localizer}
          onEventDrop={this.onEventDrop}
          onEventResize={this.onEventResize}
          resizable
          style={{ height: "100vh" }}
        />
      </div>
    );
  }
}

export default App;

to add create the DndCalendar component with the HOC.

The props are the same as the other examples.

resizable makes the calendar resizable.

Conclusion

The react-big-calendar package is an easy to use calendar package for React apps.

Categories
Vue

Storing Session Data with the Vue Session Plugin

Storing session data can be made easier with the Vue Session plugin

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

Getting Started

We install the package by running:

npm i vue-session

Then we can register in main.js by writing:

import Vue from "vue";
import App from "./App.vue";
import VueSession from "vue-session";
Vue.use(VueSession);
Vue.config.productionTip = false;

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

Then we can use 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>
import axios from "axios";
export default {
  data() {
    return {
      email: "",
      password: ""
    };
  },
  methods: {
    async login() {
      const { email, password } = this;
      const {
        data: { token }
      } = await axios.post(
        "https://ClosedThirdPixels--five-nine.repl.co/auth/login",
        {
          password: password,
          email: email
        }
      );
      this.$session.start();
      this.$session.set("jwt", token);
    }
  }
};
</script>

We created a form to let us log in.

In the login method, we make a POST request with Axios.

Once it succeeds, we call the $session.start method to create the session.

Our back end can be something like:

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({
    token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
  })
});

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

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

to return the token.

We would return a token only when the username and password are valid.

And then we can populate it with our own key-value pairs.

To get the data from the session, we can use the this.$session.get method.

For example, we can write:

$session.get('jwt')

to get the session data with the jwt key.

To check if the session exists, we call this.$session.exists() .

To remove something with the given key, we call this.$session.remove() .

To clear all the data, we call this.$session.clear() .

this.$session.id() returns the session ID.

this.$session.renew() renews the session.

this.$session.destroy() destroys a session.

So we can write:

<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">
      <button type="button" @click="logout">log out</button>
    </form>
  </div>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      email: "",
      password: ""
    };
  },
  methods: {
    async login() {
      const { email, password } = this;
      const {
        data: { token }
      } = await axios.post(
        "https://ClosedThirdPixels--five-nine.repl.co/auth/login",
        {
          password: password,
          email: email
        }
      );
      this.$session.start();
      this.$session.set("jwt", token);
    },
    logout() {
      this.$session.destroy();
    }
  }
};
</script>

to add a logout method to destroy the session with this.session.$destroy() .

Flash

We can use the flash property to save data until we read them without having to start a regular session.

this.$session.flash.set(key, value) sets a flash value.

this.$session.flash.get(key) reads and removes a flash value.

this.$session.flash.remove(key) removes a flash value.

Conclusion

Vue Session is an easy to use library for storing sessions within a Vue app.

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.