loading...

Bulma Based UI Components for Vue.js

aligoren profile image Ali GOREN ・5 min read

Buefy Logo

This post was first posted on my blog

Hi. In this post I’ll talk about Buefy. If you are using Vue.js on your projects you’ll love Buefy. Because it is based on the Bulma Framework. Bulma is an open source CSS framework based on Flexbox. I can say Tesla using Bulma on their some projects. If you’re bored with Bootstrap, it might be a good choice for you. There are many modern UI components in Buefy.

Bulma Based UI Components for Vue.js

Before you start, you need to install Vue.js as you know. After that you can install Buefy with below command.

npm install buefy

That’s all. We’ve install Buefy now. Now we’ll do some configuration on our main.js file to use Buefy. Firstly we’ll import Buefy as component and its css file

import Buefy from 'buefy'
import 'buefy/lib/buefy.css'

We’ll pass Buefy component to vue’s use method. For example our main.js file should be like this:

import Vue from 'vue'
import App from './App'
import router from './router'
import Buefy from "buefy"
import 'buefy/lib/buefy.css'

Vue.use(Buefy)

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

Ok we’ve done everything to use Buefy. Let’s start with a simple example. I’ll create basic navbar component with Buefy.

<template>
    <div>
        <nav class="navbar is-dark" role="navigation" aria-label="dropdown navigation">
            <div class="navbar-brand">
                <a class="navbar-item not-affect" href="/#/" >
                    <!-- <img :src="logo" alt="Book Reads" width="112" height="28"> -->
                    <span><i class="mdi mdi-24px mdi-home"></i> Home</span>
                </a>
                <div class="navbar-burger burger" v-on:click="showNav = !showNav" v-bind:class="{ 'is-active' : showNav }" data-target="navbarExampleTransparentExample">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
            </div>
            <div class="navbar-menu" v-bind:class="{ 'is-active' : showNav }">
                <div class="navbar-start">
                    <a class="navbar-item">
                        <span><i class="mdi mdi-24px mdi-library-books"></i> Books</span>
                    </a>
                    <a class="navbar-item">
                        <span><i class="mdi mdi-24px mdi-comment-text-outline"></i> Book Reviews</span>
                    </a>
                    <a class="navbar-item">
                        <span><i class="mdi mdi-24px mdi-account-multiple"></i> Users</span>
                    </a>
                    <div class="navbar-item has-dropdown is-hoverable" @click="showSubMenu()">
                        <a class="navbar-link"><span><i class="mdi mdi-24px mdi-view-list"></i> Books</span></a>
                        <div class="navbar-dropdown is-hidden-touch">
                            <a class="navbar-item" href=""><span><i class="mdi mdi-24px mdi-star"></i> Favorites</span></a>
                        </div>
                    </div>
                </div>
                <div class="navbar-end">
                    <a v-if="navLogin" class="navbar-item" @click="openLogin()">
                        Login
                    </a>
                    <router-link v-if="navLogin" class="navbar-item" to="/register">Signup!</router-link>
                    <!-- <div class="navbar-item ">
                        <div class="field is-grouped">
                            <p class="control">
                                <router-link class="button is-outlined" to="/add-book">
                                    <span>Add New Book</span>
                                </router-link>
                            </p>
                        </div>
                    </div> -->
                </div>
            </div>
        </nav>
    </div>
</template>

And its script should be like this:

import LoginModal from "../modals/LoginModal"

export default {
    name: 'Navbar',
    props: ['hideNavLogins'],
    data() {
        return {
            showNav: false,
            logo: './static/logo.png',
            navLogin: true
        }
    },
    components: {
        LoginModal
    },
    mounted() {

        this.navLogin = this.hideNavLogins
        if(this.hideNavLogins == undefined) {
            this.navLogin = true;
        }
    },
    methods: {
        showSubMenu() {

            let m;
            let e;
            try {
                e = event.target;
                m = event.target.nextSibling.nextSibling.classList;

            } catch (error) {
                e = event.target.parentNode;
                m = event.target.parentNode.nextSibling.nextSibling.classList;

            }

            let class1 = e.childNodes[1].classList.contains('mdi-arrow-down') ? 'mdi-arrow-down' : 'mdi-arrow-up';
            let class2 = class1 == 'mdi-arrow-down' ? 'mdi-arrow-up' : 'mdi-arrow-down';

            e.childNodes[1].classList.replace(class1, class2)

            m.toggle('is-hidden-touch')
        },
        openLogin() {
            this.$modal.open({
                parent: this,
                component: LoginModal,
                hasModalCArd: true,
                props: {
                }
            })
        },
        myMethod() {
            console.log("...")
        }
    }
}

It will look like this when you run your project:

Buefy Navbar

Now we’ll change default component’s template and it’s script section. For example we have a component named Home.vue. It’s template should be like this:

<template>
    <div>
        <navbar></navbar>
        <div class="container is-fullheight hero-body">
            <h3 class="title has-text-dark is-4">Latest Books</h3>
            <b-field grouped>
                <b-input placeholder="isbn:9780136083252 or inauthor:Robert C. Martin intitle:Clean Code or directly Clean Code" v-model="search" type="text" expanded></b-input>
                <p class="control">
                    <button class="button is-primary" @click="searchBooks">Search</button>
                </p>
            </b-field>
            <div class="columns is-centered">
                <div class="column is-12">
                    <div class="box">
                        <section>
                            <div class="columns is-multiline">
                                <div class="column is-4" v-for="book in books" :key="book.id">
                                    <div class="card">
                                        <div class="card-image">
                                            <figure class="image is-4by3">
                                                <img v-bind:src="book.volumeInfo.imageLinks ? book.volumeInfo.imageLinks.thumbnail : ''"  v-bind:alt="book.volumeInfo.title">
                                            </figure>
                                        </div>
                                        <div class="card-content">
                                            <div class="media">
                                                <div class="media-content">
                                                    <p class="title is-6 has-text-danger">{{ book.volumeInfo.title }}</p>
                                                    <strong class="">{{ book.volumeInfo.subtitle }}</strong>
                                                </div>
                                            </div>

                                            <div class="content">
                                                <p>{{ (book.volumeInfo.description) ? book.volumeInfo.description.substring(0,100) + '...' : '' }}</p>
                                            <br>
                                            <time v-bind:datetime="book.volumeInfo.publishedDate">{{ book.volumeInfo.publishedDate }}</time>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </section>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

And its script should be like this:

import Navbar from "./features/Navbar"

export default {
    name: 'Dashboard',
    data() {
        return {
            search: '',
            desc: '',
            books: []
        }
    },
    components: {
        Navbar
    },
    mounted() {
        this.fetchBooks();
    },
    methods: {
        fetchBooks() {
        },
        async searchBooks() {
            let link = `${this.$api.links['google-search']}${encodeURIComponent(this.search)}&key=AIzaSyAcotR8YZ-Zsd6dcREUBhkUA_NE3UC5AIY`

            const data = await fetch(link).then(resp => resp.json())

            this.books = data.items

        },
    },
}

In this example we are using google books api. Now, our home page should be like this:

Buefy Home

Let’s Create Login Modal

We’ll create login modal. Buefy has the JavaScript API to use modal. In this example we’ll create a modal component named LoginModal.

<template>
    <div class="modal-card">
        <section class="modal-card-body">
            <login-logo></login-logo>
            <h3 class="title has-text-centered has-text-dark">Member Login</h3>
            <div class="box">
                    <b-field label="E-Mail">
                        <b-input v-model="mail" type="email" placeholder="E-Mail">
                        </b-input>
                    </b-field>

                    <b-field label="Password">
                        <b-input v-model="password" type="password" placeholder="Password" minlength="6" password-reveal>
                        </b-input>
                    </b-field>
                    <b-field>
                        <a class="password-remind-link has-text-dark is-pulled-right" @click="passwordReminder()">I forgot my password</a>
                    </b-field>
                    <button class="button is-dark is-large is-fullwidth" @click="doLogin()">
                        Login
                    </button>
                </div>
                <div class="has-text-centered">
                    <router-link v-on:click.native="closeModal()" to="/register">Signup!</router-link>
                </div>
        </section>
    </div>
</template>

And its script will be like this:

import LoginLogo from "../features/LoginLogo"
import PasswordForgot from "./PasswordForgot"

export default {
  name: 'LoginModal',
  data () {
    return {
      mail: '',
      password: '',
    }
  },
  components: {
    LoginLogo,
    PasswordForgot
  },
  methods: {
    passwordReminder() {
        this.$parent.close()
        this.$modal.open({
            parent: this,
            component: PasswordForgot,
            hasModalCArd: true,
            props: {
            }
        })
    },
    closeModal() {
        this.$parent.close()
    },
    doLogin() {
        this.$parent.close()
        this.$router.push('/dashboard')
    }
  }
}

Our modal will be like this:

Buefy Modal

Nice. We’ve created basic bookreads portal alternative to goodreads. But Buefy’s has more component. For example its table component alternative to jQuery DataTable.

What else can we do?

We can create book review component. For example users will use BookReview component to send their comments. In order to access this component we can use routing mechanism etc.

Conclusion

I published the bookreads project on GitLab. You can use directly or you can fork it to contribute it.

BookReads Project on GitLab

In this post we talked about Buefy and Vue. You can create great projects with Buefy and Vue. If you have any questions please ask

Thanks for reading.

Posted on by:

aligoren profile

Ali GOREN

@aligoren

I'm a front-end developer. I'm living in Turkey. I started my professional career in 2016. I also interest in Backend, SQL technologies.

Discussion

pic
Editor guide
 

Thanks Ali for this great post. I followed your approach with the Navbar hamburger for toggling menu on mobile screen. The challenge I'm facing now is that the dropdown menu keeps showing after clicking on one of the links on the navbar menu. I'm using this inside a Nuxtjs project.

See below what the code looks like

<template>
  <nav class="navbar has-shadow is-fixed-top" role="navigation" aria-label="main navigation">
    <div class="container">
      <div class="navbar-brand">
        <nuxt-link to="/" class="navbar-item">
          <img class="logo" src="/img/logo.png">
        </nuxt-link>

        <a
          role="button"
          class="navbar-burger burger"
          aria-label="menu"
          aria-expanded="false"
          @click="showMenu = !showMenu"
          :class="{ 'is-active' : showMenu }"
          data-target="customNVMenu"
        >
          <span aria-hidden="true"></span>
          <span aria-hidden="true"></span>
          <span aria-hidden="true"></span>
        </a>
      </div>

      <div id="customNVMenu" class="navbar-menu" :class="{ 'is-active' : showMenu }">
        <div class="navbar-end">
          <nuxt-link to="/" class="navbar-item">Home</nuxt-link>
          <nuxt-link to="/about" class="navbar-item">About</nuxt-link>         
          <nuxt-link to="/login" class="navbar-item">Log In</nuxt-link>
          <div class="navbar-item">
            <div class="buttons">
              <nuxt-link to="/register" class="button is-primary">Register</nuxt-link>
            </div>
          </div>
        </div>
      </div>
    </div>
  </nav>
</template>

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

How can i toggle the showMenu property to false when any of the navbar menu item is clicked?

Thank you

 

Hi Ali,
There is an outdated instruction in your post:
It should be: import 'buefy/dist/buefy.css'
(instead of the old: import 'buefy/lib/buefy.css'

Regards, Marc