Categories
Buefy

Buefy — Modals

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.

Modal

Buefy comes with a modal component.

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

For example, we can write:

<template>
  <div id="app">
    <button class="button is-primary is-medium" @click="isActive = true">Launch modal</button>
    <b-modal v-model="isActive">
      <div class="card">
        <div class="content">Lorem ipsum dolor sit amet</div>
      </div>
    </b-modal>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      isActive: true
    };
  }
};
</script>

We put a card inside the b-modal to show its content.

We can call the close method to close the modal.

For example, we can write:

<template>
  <div id="app">
    <button class="button is-primary is-medium" @click="isActive = true">Launch modal</button>
    <b-modal v-model="isActive">
      <template #default="props">
        <button @click="props.close">close</button>
      </template>
    </b-modal>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      isActive: true
    };
  }
};
</script>

to add the Close button that calls props.close to close the modal.

We can open a modal programmatically with the this.$vuefy.modal.open method:

Login.vue

<template>
  <div class="modal-card" style="width: auto">
    <header class="modal-card-head">
      <p class="modal-card-title">Login</p>
      <button type="button" class="delete" @click="$emit('close')"/>
    </header>
    <section class="modal-card-body">
      <b-field label="Email">
        <b-input type="email" :value="email" placeholder="Your email" required></b-input>
      </b-field>

<b-field label="Password">
        <b-input type="password" required></b-input>
      </b-field>
    </section>
    <footer class="modal-card-foot">
      <button class="button" type="button" @click="$emit('close')">Close</button>
      <button class="button is-primary">Login</button>
    </footer>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      email: "",
      password: ""
    };
  }
};
</script>

App.vue

<template>
  <div id="app">
    <button class="button is-primary is-medium" @click="open">Launch modal</button>
    <b-modal>
      <template #default="props">
        <button @click="props.close">close</button>
      </template>
    </b-modal>
  </div>
</template>

<script>
import Login from "@/components/Login.vue";
export default {
  name: "App",
  data() {
    return {};
  },
  methods: {
    open() {
      this.$buefy.modal.open({
        parent: this,
        component: Login,
        hasModalCard: true,
        customClass: "custom-class",
        trapFocus: true
      });
    }
  }
};
</script>

We add the Login.vue component with a login form.

Login.vue emits the close event to close the modal when we click the close button or the footer buttons.

And we use that with the this.$buefy.modal.open method to show the Login component in the modal.

parent has the parent component, which is set to the current component.

We can add the full-screen prop to make the modal fullscreen:

<template>
  <div id="app">
    <b-modal full-screen v-model="isActive">
      <Login/>
    </b-modal>
  </div>
</template>

<script>
import Login from "@/components/Login.vue";
export default {
  name: "App",
  data() {
    return {
      isActive: true
    };
  },
  methods: {},
  components: {
    Login
  }
};
</script>

Conclusion

We can add modals with Buefy’s b-modal component.

Categories
Buefy

Buefy — Message Box

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.

Message

We can add a message box with color with Buefy.

We can add it with the b-message component:

<template>
  <div id="app">
    <b-message title="Default" v-model="isActive">Lorem ipsum dolor sit amet</b-message>
  </div>
</template>

<script>
export default {
  name: "App",
  data(){
    return {
      isActive: true
    }
  }
};
</script>

title has the title.

v-model lets us control the open and close state.

The default slot has the content.

The color scheme can be changed with the type prop:

<template>
  <div id="app">
    <b-message type="is-success" title="Default" v-model="isActive">Lorem ipsum dolor sit amet</b-message>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      isActive: true
    };
  }
};
</script>

We can add an icon with the has-icon prop:

<template>
  <div id="app">
    <b-message
      type="is-success"
      has-icon
      title="Default"
      icon="address-book"
      icon-pack="fa"
      v-model="isActive"
    >Lorem ipsum dolor sit amet</b-message>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      isActive: true
    };
  }
};
</script>

We add the icon , icon-pack , and has-icon props to show the icon.

icon-pack is the icon pack name.

'fa' is Font Awesome.

icon has the icon class name.

In public/index.html , we add:

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

to the head tag to make the Font Awesome icons available.

The title prop is optional, so we can remove it to show a message box without the header:

<template>
  <div id="app">
    <b-message v-model="isActive">Lorem ipsum dolor sit amet</b-message>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      isActive: true
    };
  }
};
</script>

The size prop changes the size:

<template>
  <div id="app">
    <b-message size="is-large" v-model="isActive">Lorem ipsum dolor sit amet</b-message>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      isActive: true
    };
  }
};
</script>

The auto-close prop makes it close automatically:

<template>
  <div id="app">
    <b-message auto-close v-model="isActive">Lorem ipsum dolor sit amet</b-message>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      isActive: true
    };
  }
};
</script>

The duration that it’s visible can be set with the duration prop:

<template>
  <div id="app">
    <b-message :duration="2000" auto-close v-model="isActive">Lorem ipsum dolor sit amet</b-message>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      isActive: true
    };
  }
};
</script>

The number is in milliseconds.

Conclusion

We can add a message box with Buefy’s b-message component.

Categories
Buefy

Buefy — Loading Indicator and Menu

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.

Loading Indicator

We can add a loading indicator with the b-loading component:

<template>
  <div id="app">
    <b-loading is-full-page v-model="isLoading" :can-cancel="true"></b-loading>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      isLoading: true
    };
  }
};
</script>

is-full-page sets the loading indicator to fill the page.

v-model binds to the isLoading state to let us control when it’s displayed.

can-cancel lets us close it with the Esc key with when it’s set to true .

We can open the loading indicator programmatically with the this.$buefy.loading.open method:

<template>
  <div id="app" ref="element">
    <button class="button is-primary is-medium" @click="open">load</button>
  </div>
</template>

<script>
export default {
  name: "App",
  methods: {
    open() {
      const loadingComponent = this.$buefy.loading.open({
        container: this.$refs.element.$el
      });
      setTimeout(() => loadingComponent.close(), 3 * 1000);
    }
  }
};
</script>

We call the method with the container property.

It sets the container for displaying the loading indicator.

We can set it to null to make it fill the screen.

The close method closes the loading indicator.

Also, we can create our own loading indicator by populating the default slot:

<template>
  <div id="app" ref="element">
    <b-loading is-full-page v-model="isLoading" :can-cancel="true">
      <b-icon pack="fa" icon="circle-o-notch" size="is-large" custom-class="fa-spin"></b-icon>
    </b-loading>
  </div>
</template>

<script>
export default {
  name: "App",
  methods: {
    isLoading: true
  }
};
</script>

We set the loading indicator to an icon with the b-icon component.

fa-spin makes Font Awesome icons spin.

Menu

Buefy comes with a menu component.

For example, we can write:

<template>
  <div id="app" ref="element">
    <b-menu>
      <b-menu-list label="Menu">
        <b-menu-item icon="address-book" icon-pack="fa" label="Info"></b-menu-item>
        <b-menu-item icon="settings" :active="isActive" expanded>
          <template slot="label" slot-scope="props">
            Administrator
            {{props.expanded ? '&#x2193;' : '&#x2191;'}}
          </template>
          <b-menu-item label="Users"></b-menu-item>
          <b-menu-item>
            <template slot="label">Devices
              <b-dropdown class="is-pulled-right" position="is-bottom-left">
                <b-icon slot="trigger"></b-icon>
                <b-dropdown-item>action 1</b-dropdown-item>
                <b-dropdown-item>action 2</b-dropdown-item>
              </b-dropdown>
            </template>
          </b-menu-item>
          <b-menu-item label="Payments" disabled></b-menu-item>
        </b-menu-item>
        <b-menu-item label="My Account">
          <b-menu-item label="Account data"></b-menu-item>
          <b-menu-item label="Addresses"></b-menu-item>
        </b-menu-item>
      </b-menu-list>
      <b-menu-list>
        <b-menu-item label="Expo" tag="router-link" target="_blank" to="/expo"></b-menu-item>
      </b-menu-list>
      <b-menu-list label="Actions">
        <b-menu-item label="Logout"></b-menu-item>
      </b-menu-list>
    </b-menu>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return { isActive: true };
  }
};
</script>

We add the b-menu to add the menu.

b-menu-item has the menu items.

b-dropdown adds dropdowns.

b-menu-list is a menu list to separate the menu into sections.

Conclusion

We can add a loading indicator and menu easily with Buefy.

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.