Last Update, Honest, to My Vue.js INeedIt Demo

Ok, I know I said my last post on my Vue.js INeedIt app was the last post, but I had an idea for just one more tweak and couldn't resist taking a stab at it. It didn't quite work out the way I wanted it too, but it was an interesting iteration to the app and I think now I can put it down and move on to the next thing I want to build. For this final version of the app, I decided to apply a bit of UI to make it look a bit nicer. I thought Bootstrap would be great for this, and I was excited to discover that there was actually a Bootstrap + Vue project that made this easy (somewhat).

Bootstrap + Vue = Love

Bootstrap-Vue lets you use Bootstrap in your project via components. I really like Bootstrap, but I can never remember the class names and such to use certain features. But the component version is much easier to use I think and it just feels a lot more natural.

As an example, check out how tabs look:

See the Pen bootstrap-vue tabs by Raymond Camden (@cfjedimaster) on CodePen.

I really, really like that format. Actually using the library requires a few steps. This is covered in the Getting Started guide. Since I had a Webpack app, I followed the instructions there. I don't know about you, but it still weirds me out to "install" a client-side library via npm. Even more weird was when I used import statements to load the CSS. It worked, but it just felt... odd. This is how my main.js looks now:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.use(BootstrapVue)

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  template: '',
  components: { App }
})

The new lines are right on top. I import Bootstrap-vue and then the CSS. And literally that was it. So for example, here is the new initial page that lists the types of services available:

Screenshot

Here is the template portion of the component:

<template>
    <div>

        <h1>Service List</h1>
        <div v-if="loading">
        Looking up your location...
        </div>

        <div v-if="error">
        I'm sorry, but I had a problem getitng your location. Check the console for details.
        </div>

        <div v-if="!loading && !error">
            <b-list-group>
                <b-list-group-item v-for="service in serviceTypes" :key="service.id">
                    <router-link :to="{name:'typeList', params:{type:service.id, name:service.label, lat:lat, lng:lng} }">{{service.label}}</router-link>
                </b-list-group-item>
            </b-list-group>
        </div>

    </div>
</template>

It isn't that much different - mainly just the new b-list-group and b-list-group-item tags.

The service listing page makes use of the same component with the addition of a button to return back. Note the "blue link" border around it. I'm not sure why that's there (and it won't be in the next view), but as I was tired of fighting other issues (more on that later) I just didn't bother correcting it.

Screenshot

Here's that template:

<template>
    <div>

        <h1>{{name}}</h1>

        <div v-if="loading">
        Looking up data...
        </div>

        <div v-if="!loading">
            <b-list-group>
                <b-list-group-item v-for="result in results" :key="result.id">
                <router-link :to="{name:'detail', params:{placeid:result.place_id} }">{{result.name}}</router-link>
                </b-list-group-item>
            </b-list-group>

            <p v-if="results.length === 0">
            Sorry, no results.
            </p>

            <p>
            <b-button block variant="primary" to="/" style="margin-top:10px">Go Back</b-button>
            </p>
        </div>

    </div>
</template>

Finally - I decided on a "card" interface for the result page. This is where I ran into the most trouble. First, I ran into issues with Google's API for Google Places. I set up a new key to use their Photo API and the Static Maps API. This key was restricted to localhost and my GitHub repo. Unfortunately, it never worked consistently. Sometimes both were blocked with 403 errors and sometimes just the place pictures were. I reported it to a friend at Google, but because of this, I can't share a public version of the app. I then ran into sizing issues using the card component. I switched to the carousel and had sizing issues there. I took my best stab at it and it's ok, but still not right. But I think it's pretty cool.

Anyway - here is an example:

Screenshot

Scrolling down, the pictures are in a carousel you can browse.

Screenshot

Here's the display code:

<template>
    <div>
        <div v-if="loading">
        Looking up data...
        </div>

        <div v-if="!loading">

            <b-card :title="detail.name" :sub-title="detail.formatted_address">

                <p class="card-text">
                    This business is currently 
                    <span v-if="detail.opening_hours">
                        <span v-if="detail.opening_hours.open_now">open.</span><span v-else>closed.</span>
                    </span>
                    <br/>
                    Phone: {{detail.formatted_phone_number}}<br/>
                    Website: <a :href="detail.website" target="_new">{{detail.website}}</a><br/>
                    <span v-if="detail.price">Items here are generally priced "{{detail.price}}".</span>
                </p>

                <p>
                <img :src="detail.mapUrl" width="310" height="310" class="full-image" />
                </p>

                <b-carousel id="carousel1"
                controls
                indicators
                :interval="0"
                >
                      <b-carousel-slide 
                        v-for="img in detail.photos" :key="img.url" style="height:300px">
                            <img slot="img" :src="img.url" class="d-block img-fluid w-100" style="overflow:hidden">
                      </b-carousel-slide>
                </b-carousel>
            </b-card>

            <b-button block variant="primary" @click.prevent="goBack" style="margin-top:10px">Go Back</b-button>
        </div>
    </div>
</template>

Pretty cool - even with the issues I ran into. You can browse the entire code base here: https://github.com/cfjedimaster/webdemos/tree/master/ineedit4

I hope this progresive look at one app has been helpful to others. Now I'm ready to leave this demo behind and get to work om my next one!

Like This?

If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can also subscribe to the email feed to get notified of new posts.

See Also