Categories
Buefy

Buefy — Icons and Images

Buefy is a UI framework that’s based on Bulma.

In this article, we’ll look at how to use Buefy in our Vue app.

Icons

We can add icons with the b-icon component.

To do that, we add our icon CSS:

<link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
      integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN"
      crossorigin="anonymous"
    />

into our head tag of the public/index.html file.

Then in our component, we write:

<template>
  <div id="app">
    <b-icon icon="address-book" pack="fa" size="is-small"></b-icon>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

pack sets the icon pack to use.

'fa' sets it to Font Awesome.

icon is the icon name that we use.

size sets the size.

We can add the type ptop to set the style:

<template>
  <div id="app">
    <b-icon type="is-success" icon="address-book" pack="fa" size="is-small"></b-icon>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

We can also write it as an object:

<template>
  <div id="app">
    <b-icon :type="{ 'is-success': true }" icon="address-book" pack="fa" size="is-small"></b-icon>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

This lets us set the style conditionally.

Image

Buefy comes with the b-image component to let us add images.

For example, we can write:

<template>
  <div id="app">
    <b-image src="https://picsum.photos/600/400" alt="image" ratio="6by4" rounded></b-image>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

alt sets the text description for the image.

src has the URL of the image.

rounded makes the image rounded.

ratio is the aspect ratio.

We can also add WebP images with b-image :

<template>
  <div id="app">
    <b-image
      src="https://picsum.photos/id/237/800/450.webp"
      webp-fallback="https://picsum.photos/id/1025/800/450.jpg"
      alt="image"
      ratio="6by4"
      rounded
    ></b-image>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

webp-fallback has the URL for the fallback image in case WebP images aren’t supported in your browser.

We can add srcset props to show different images when the screen is different sizes:

<template>
  <div id="app">
    <b-image
      src="https://picsum.photos/id/1074/1600/800"
      :srcset-sizes="[400, 800, 1600]"
      :srcset-formatter="this.formatSrcset"
      alt="image"
      ratio="6by4"
    ></b-image>
  </div>
</template>

<script>
export default {
  name: "App",
  methods: {
    formatSrcset(src, size) {
      return `https://picsum.photos/id/1000/${size}/${size / 2}`;
    }
  }
};
</script>

Also, we can listen to the load event and run an event handler when it loads:

<template>
  <div id="app">
    <b-image src="https://picsum.photos/id/1074/1600/800" alt="image" ratio="6by4" @load="onLoad"></b-image>
  </div>
</template>

<script>
export default {
  name: "App",
  methods: {
    onLoad(event, src) {
      console.log(src);
    }
  }
};
</script>

onLoad is run when the load event is emitted.

We can hamdle errors by listening to the error event and add a fallback image:

<template>
  <div id="app">
    <b-image
      src="https://picsum.photos/id/error/600/400"
      src-fallback="https://picsum.photos/id/237/600/400"
      ratio="6by4"
      @error="onError"
    ></b-image>
  </div>
</template>

<script>
export default {
  name: "App",
  methods: {
    onError(event, src) {
      console.log(`${src} fails to load`);
    }
  }
};
</script>

The onError method is run when the image set in the src attribute fails to load.

Conclusion

We can display icons and images easily with Buefy.

Categories
Nuxt.js

Adding Authentication to a Nuxt App with the Firebase

With the Nuxt Auth module, we can add authentication to our Nuxt app with ease.

One way is to add authentication with Firebase.

In this article, we’ll look at how to add Firebase authentication to our server-side rendered Nuxt app.

Install Packages

We have to add some packages to add Firebase to our Nuxt app.

To do that, we run:

npm i @nuxtjs/firebase @nuxtjs/pwa firebase firebase-admin

to add the required packages.

Configuration

We have to add configuration to our Nuxt app so that we can get the user when we load pages.

In nuxt.config.js , we write:

export default {
  /*
  ** Nuxt rendering mode
  ** See https://nuxtjs.org/api/configuration-mode
  */
  mode: 'universal',
  /*
  ** Nuxt target
  ** See https://nuxtjs.org/api/configuration-target
  */
  target: 'server',
  /*
  ** Headers of the page
  ** See https://nuxtjs.org/api/configuration-head
  */
  head: {
    title: process.env.npm_package_name || '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  /*
  ** Global CSS
  */
  css: [
  ],
  /*
  ** Plugins to load before mounting the App
  ** https://nuxtjs.org/guide/plugins
  */
  plugins: [
  ],
  /*
  ** Auto import components
  ** See https://nuxtjs.org/api/configuration-components
  */
  components: true,
  /*
  ** Nuxt.js dev-modules
  */
  buildModules: [
  ],
  /*
  ** Nuxt.js modules
  */
  modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    '@nuxtjs/pwa',
    [
      '@nuxtjs/firebase',
      {
        config: {
          apiKey: "api-key",
          authDomain: "project-id.firebaseapp.com",
          databaseURL: "https://project-id.firebaseio.com",
          projectId: "project-id",
          storageBucket: "project-id.appspot.com",
          messagingSenderId: 'message-sender-id',
          appId: "BookDb"
        },
        services: {
          auth: {
            persistence: 'local',
            initialize: {
              onAuthStateChangedMutation: "SET_USER",
              onAuthStateChangedAction: 'onAuthStateChangedAction',
            },
            ssr: {
              serverLogin: {
                sessionLifetime: 60 * 60 * 1000,
                loginDelay: 50
              }
            }
          }
        }
      }
    ]
  ],
  /*
  ** Axios module configuration
  ** See https://axios.nuxtjs.org/options
  */
  axios: {

},
  /*
  ** Build configuration
  ** See https://nuxtjs.org/api/configuration-build/
  */
  build: {
  },
  firebase: {
    services: {
      auth: {
        ssr: true
      }
    }
  },
  pwa: {
    meta: false,
    icon: false,
    workbox: {
      importScripts: [
        '/firebase-auth-sw.js'
      ],
      dev: true
    }
  }
}

We add the @nuxtjs/firebase module with many options.

The config property has the Firebase configuration options.

services configures the auth service to persist the user.

The onAuthStateChangeMutation action is the Vuex action to save the authenticated user’s data.

ssr has the options for how long to keep the user data.

The sessionLifetime property has the session lifetime.

And loginDelay is the delay to save the user data after a successful login.

Vuex Store

We’ve to create a Vuex store to store the data.

To do that, we create a store/index.js file and write:

export const state = () => ({
  authUser: {}
})

export const actions = {
  async onAuthStateChangedAction({ commit }, { authUser, claims }) {
    const { uid, email, emailVerified, displayName, photoURL } = authUser

  commit('SET_USER', {
      uid,
      email,
      emailVerified,
      displayName,
      photoURL,
      isAdmin: claims.custom_claim
    })
  },
  async nuxtServerInit({ dispatch, commit }, { res }) {
    console.log(res.locals)
    if (res && res.locals && res.locals.user) {
      const { allClaims: claims, idToken: token, ...authUser } = res.locals.user
      await dispatch('onAuthStateChangedAction', {
        authUser,
        claims,
        token
      })
      commit('ON_AUTH_STATE_CHANGED_MUTATION', { authUser, claims, token })
    }
  }
}

export const mutations = {
  ON_AUTH_STATE_CHANGED_MUTATION(state, { authUser, claims }) {
    const { uid, email, emailVerified, displayName, photoURL } = authUser

    state.authUser = {
      uid,
      displayName,
      email,
      emailVerified,
      photoURL: photoURL || null,
      isAdmin: claims.custom_claim
    }
  },
  SET_USER(state, payload) {
    console.log(payload)
    state.authUser = payload;
  }
}

The onAuthStateChanged action is invoked by nuxtServerInit to set the authenticated user’s data on page load.

nuxtServerInit is run when the page loads.

ON_AUTH_STATE_CHANGED_MUTATION is a mutation to save the authUser state.

SET_USER sets the authUser state.

When we run the this.$fireAuth.signInWithEmailAndPassword method is run successful by authentication with the correct credentials, then the actions and mutations will be run.

The res.locals.user property in the nuxtServerInit will also be set so that we have the currently logged in user’s data on page load.

Login Form

Finally, we need a login form so we can log in.

We create a login.vue file and add:

<template>
  <div class="container">
    <form @submit="signIn">
      <div>
        <label>Username</label>
        <input type="text" v-model="login.email" />
      </div>
      <div>
        <label>Password</label>
        <input type="text" v-model="login.password" />
      </div>
      <div>
        <button type="submit">Submit</button>
      </div>
    </form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      login: {},
    };
  },
  methods: {
    async signIn() {
      try {
        const { email, password } = this.login;
        await this.$fireAuth.signInWithEmailAndPassword(email, password);
      } catch (error) {
        console.log(error);
      }
    },
  },
};
</script>

We create a login form and if we log in with the right email and password, the Vuex actions will be run to set the data.

This is because the static/assets/firebase-auth-sw.js service worker that comes with the Nuxt Firebase does that work for us in the background.

Categories
Nuxt.js

Adding Authentication to a Nuxt App with the Nuxt Auth Module

With the Nuxt Auth module, we can add authentication to our Nuxt app with ease.

In this article, we’ll look at how to add authentication to our Nuxt app with Express and the Nuxt Auth module.

Getting Started

We’ve to add the Axios and Nuxt Auth modules.

Nuxt Auth uses Axios for sending requests.

To install it, we run:

yarn add @nuxtjs/auth @nuxtjs/axios

or:

npm install @nuxtjs/auth @nuxtjs/axios

Then in nuxt.config.js , we add:

const baseURL = "https://ReasonableRecursiveSupercollider--five-nine.repl.co";

export default {
  mode: 'universal',
  target: 'server',
  head: {
    title: process.env.npm_package_name || '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  css: [
  ],
  plugins: [
  ],
  components: true,
  buildModules: [
  ],
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/auth'
  ],
  axios: {
    baseURL
  },
  build: {
  }
}

We add the baseURL to the axios property to set the base URL of our requests.

Also, we add the '@nuxtjs/auth' module to the modules array to add the module.

In the store folder, we’ve to add index.js to use the Nuxt auth module.

Back End

We use Express as the back end for our app.

To create it, we install Express with some packages by running:

npm i express body-parser cors

in a project folder.

Then we create index.js in the same folder and add:

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('/api/auth/login', (req, res) => {
  res.json({});
});

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

app.get('/api/auth/user', (req, res) => {
  res.json({});
});

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

We need the cors middleware to accept cross-domain requests.

The routes are the endpoints we send our auth requests to.

We should add logic to check for users in real-world applications.

Front End

In our Nuxt app, we add a login.vue component and add:

<template>
  <div class="container">
    <form @submit.prevent="userLogin">
      <div>
        <label>Username</label>
        <input type="text" v-model="login.username" />
      </div>
      <div>
        <label>Password</label>
        <input type="text" v-model="login.password" />
      </div>
      <div>
        <button type="submit">Submit</button>
      </div>
    </form>
  </div>
</template>

<script>
export default {
  middleware: "auth",
  data() {
    return {
      login: {},
    };
  },
  auth: {
    strategies: {
      local: {
        endpoints: {
          login: {
            url: `/api/auth/login`,
            method: "post",
            propertyName: "token",
          },
          logout: { url: `/api/auth/logout`, method: "post" },
          user: {
            url: `/api/auth/user`,
            method: "get",
            propertyName: "user",
          },
        },
      },
    },
  },
  methods: {
    async userLogin() {
      try {
        let response = await this.$auth.loginWith("local", {
          data: this.login,
        });
        console.log(response);
      } catch (err) {
        console.log(err);
      }
    },
  },
};
</script>

It has a login form that takes the username and password.

In the component options, we enable the auth middleware with the middleware: 'auth' property.

Then we set the auth options by adding the auth.strategies.local property to add the endpoints, we’ll make requests to.

login is used by the this.$auth.loginWith method to make requests.

auth.strategies.local optionally takes the tokenRequired and tokenType properties.

tokenRequired: false disables all token handling.

tokenType is the authorization header name to be used in Axios requests.

this.$auth.loginWith takes the strategy as the first argument and the data as the 2nd argument with the data property.

So when we submit the form, the userLogin method is called and the request to https://ReasonableRecursiveSupercollider--five-nine.repl.co/api/auth/login is made with the username and password in the request payload.

Conclusion

We can add a login form with the Nuxt and use the auth module to make the requests.

Categories
Buefy

Buefy — Time Picker and File Input

Buefy is a UI framework that’s based on Bulma.

In this article, we’ll look at how to use Buefy in our Vue app.

Time Picker

Buefy has a time picker component.

We can add the b-timepicker component to use it.

For example, we can write;

<template>
  <section>
    <b-field>
      <b-timepicker
        rounded
        placeholder="Click to select..."
        enable-seconds
        hour-format="12"
        locale="en"
      ></b-timepicker>
    </b-field>
  </section>
</template>

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

The component takes a few props.

rounded makes the input box have round corners.

placeholder has the placeholder.

enable-seconds has a dropdown to pick the seconds.

hour-format sets the hour format.

locale sets the locale.

We can make the time picker editable with the editable prop:

<template>
  <section>
    <b-field>
      <b-timepicker
        rounded
        placeholder="Click to select..."
        enable-seconds
        hour-format="12"
        locale="en"
        editable
      ></b-timepicker>
    </b-field>
  </section>
</template>

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

The range of allowed values can be set with the min-time and max-time props:

<template>
  <section>
    <b-field>
      <b-timepicker placeholder="Click to select..." :min-time="minTime" :max-time="maxTime"></b-timepicker>
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    const min = new Date();
    min.setHours(0);
    min.setMinutes(0);
    const max = new Date();
    max.setHours(2);
    max.setMinutes(0);
    return {
      minTime: min,
      maxTime: max
    };
  }
};
</script>

The footer can be changed by populating the default slot:

<template>
  <section>
    <b-field>
      <b-timepicker v-model="time" placeholder="Click to select...">
        <button class="button is-primary" @click="time = new Date()">
          <span>Now</span>
        </button>

        <button class="button is-danger" @click="time = null">
          <span>Clear</span>
        </button>
      </b-timepicker>
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return {
      time: new Date()
    };
  }
};
</script>

We added 2 buttons into the default slot and they’ll be displayed below the time picker.

Also, we can make it inline with the inline prop:

<template>
  <section>
    <b-field>
      <b-timepicker v-model="time" inline></b-timepicker>
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return {
      time: new Date()
    };
  }
};
</script>

The increments of the dropdown can be set with the incrementMinutes and incrementHours props:

<template>
  <section>
    <b-field>
      <b-timepicker v-model="time" :incrementMinutes="5" :incrementHours="2"></b-timepicker>
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return {
      time: new Date()
    };
  }
};
</script>

File Upload Input

The b-upload component is the file upload input.

For example, we can write:

<template>
  <b-field class="file is-primary" :class="{'has-name': !!file}">
    <b-upload v-model="file">
      <span class="file-cta">
        <span>Click to upload</span>
      </span>
      <span class="file-name" v-if="file">{{ file.name }}</span>
    </b-upload>
  </b-field>
</template>

<script>
export default {
  data() {
    return {
      file: null
    };
  }
};
</script>

to add it.

The file-cta class makes the input display as a button.

v-model binds the selected file to the file state.

We can make the file input appears as a drop zone with the drag-drop prop:

<template>
  <b-field class="file is-primary" :class="{'has-name': !!file}">
    <b-upload v-model="file" drag-drop>
      <section class="section">
        <div class="content has-text-centered">
          <p>Drop your files here or click to upload</p>
        </div>
      </section>
    </b-upload>
  </b-field>
</template>

<script>
export default {
  data() {
    return {
      file: null
    };
  }
};
</script>

The expanded prop makes the button longer:

<template>
  <b-field class="file">
    <b-upload v-model="file" expanded>
      <a class="button is-primary is-fullwidth">
        <span>{{ file && file.name || "Click to upload"}}</span>
      </a>
    </b-upload>
  </b-field>
</template>

<script>
export default {
  data() {
    return {
      file: null
    };
  }
};
</script>

Conclusion

We can add a time picker and file upload input with Buefy.

Categories
Buefy

Buefy — Customize Tag Input

Buefy is a UI framework that’s based on Bulma.

In this article, we’ll look at how to use Buefy in our Vue app.

Customize Tag Input

We can customize our tag input in various ways.

We can limit the number of tags we can add and the number of characters each can have.

The maxlength prop limits the number of characters in a tag.

The maxtags prop limits the number of tags we can enter.

For example, we can write:

<template>
  <section>
    <div class="field">
      <b-taginput v-model="tags" maxlength="10" placeholder="Add a tag"></b-taginput>
      {{tags}}
    </div>
  </section>
</template>

<script>
export default {
  data() {
    return { tags: [] };
  }
};
</script>

to limit the number of characters of each tag to 10 max.

And we can write:

<template>
  <section>
    <div class="field">
      <b-taginput v-model="tags" maxtags="5" placeholder="Add a tag"></b-taginput>
      {{tags}}
    </div>
  </section>
</template>

<script>
export default {
  data() {
    return { tags: [] };
  }
};
</script>

to limit the max number of tags to 5.

We can change the outline color with the type prop on the b-field:

<template>
  <section>
    <b-field type="is-success">
      <b-taginput v-model="tags" placeholder="Add a tag"></b-taginput>
      {{tags}}
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return { tags: [] };
  }
};
</script>

Now the outline is green.

The type prop can be added to the b-taginput to change the background color of the tags:

<template>
  <section>
    <b-field>
      <b-taginput type="is-dark" v-model="tags" placeholder="Add a tag"></b-taginput>
      {{tags}}
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return { tags: [] };
  }
};
</script>

The size prop changes the size of the tag input:

<template>
  <section>
    <b-field>
      <b-taginput size="is-large" v-model="tags" placeholder="Add a tag"></b-taginput>
      {{tags}}
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return { tags: [] };
  }
};
</script>

The rounded prop makes the tag input round:

<template>
  <section>
    <b-field>
      <b-taginput rounded v-model="tags" placeholder="Add a tag"></b-taginput>
      {{tags}}
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return { tags: [] };
  }
};
</script>

The attched prop makes the tags rectangular:

<template>
  <section>
    <b-field>
      <b-taginput attached v-model="tags" placeholder="Add a tag"></b-taginput>
      {{tags}}
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return { tags: [] };
  }
};
</script>

We can add validation with the before-adding prop:

<template>
  <section>
    <b-field>
      <b-taginput :before-adding="beforeAdding" v-model="tags" placeholder="Add a tag"></b-taginput>
      {{tags}}
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return { tags: [] };
  },
  methods: {
    beforeAdding(tag) {
      return tag.length === 3;
    }
  }
};
</script>

The beforeAdding method will be run before the tag is added.

The tag will only be added if it returns true .

tag has the entered value.

Conclusion

We can customize tag inputs the way we like with Buefy’s tag input.