In the first 3 parts of developing the vuetiful blog, we set up our development environment and configured Semantic-UI for use in our app, now it’s time to actually start designing the website. So far our app has an entry route defined in laravel’s routes/web.php which serves up our vue app, but we are going to need more than one route, so we are going to need to add vue-router to our project.

npm install vue-router --save-dev

You should be getting used to that by now. Once it’s installed we a need to add it to our main Vue instance, so let’s open up resources/assets/js/app.js and add it:

import VueRouter from 'vue-router'

Because we are using a modular system we also need to register it using Vue.use:

Vue.use(VueRouter)

We now have vue-router ready to use in our project, but we don’t yet have any components to serve up, for now let’s just create a component in app.js so we can create our first route and register it with our router:


const FrontPage = {render: function(createElement){
    return createElement('div',{},'Front Page');
}}

const routes = [
    { path: '/', component: FrontPage },
];

// Setup vue-router
const router = new VueRouter({
    mode: 'history',
    routes
})

Notice we are using history mode to remove the hashbang from the url. Let’s register that in our main vue instance, which should now look like this:

const app = new Vue({
    router,
    render: h => h(App)
}).$mount('#app');

With everything registered we need somewhere to actually serve up our component. To do this we simply need to add router-view to our base component and view router will automatically inject our component into that part of the page, so let’s open resources/assets/js/components/App.vue and add it in place of the “Semantic-UI Setup Complete!” message we wrote in part 2:

<template>
    <div class="ui center aligned padded grid">
        <div class="column">
            <div class="ui segment">
               <router-view></router-view>
            </div>
        </div>
    </div>
</template>

At this stage, fire up your app by using the script we developed in part 3:

npm run dev

And navigate to http://localhost:8000, it should now say “front page”, which tells us vue-router has been set up correctly.

Designing the Base Component

Obviously a website that just says “front page” isn’t much good to us, we need a way to navigate, so let’s start by designing a Navigation Menu.

One thing I’m going to want is to add icons to my menu, and Sementic-ui has a port of font-awesome icons, we just need to set them up.

Adding Icon Support from Semantic-UI

First we just need to copy the fonts from: resources/assets/semantic/dist/themes/default/assets/fonts to public/fonts

Semantic looks for fonts in css/themes/default/assets/fonts, so we want to change that. In the semantic folder, let’s open up src/globals/site.variables and update the font path:

@fontPath: "/fonts";

And now we need to build semantic again. It’s a bit tedious to keep navigating to the semantic directory and running gulp build, then coming back and running gulp sass, so let’s add some scripts to package.json to do this for us:

    "semantic": "cd resources/assets/semantic && gulp build",
    "sass": "gulp sass",
    "ui" : "npm-run-all --sequential semantic sass",

We can now run:

npm run ui

To rebuild semantic for us and compile our sass files and our site can now use semantic’s icon set. Check out package.json on the Github page

Creating the Navogation Menu

Now we have icons setup, let’s create a navigation menu for our site by creating a new component at: “resources/assets/js/components/core/NavMenu.vue” which looks like this:

<template>
    <div class="ui fixed menu">
        <div class="ui container">
            <router-link to="/" class="item"><i class="home icon"></i> Home</router-link>
        </div>
    </div>
</template>

The only thing to note here is that we are using router-link to create the links for us, which allows us to fallback to hash mode in IE9 and intercepts click events for us, so the browser doesn’t attempt to navigate.

So, let’s add this to App.vue. First we register it in our components property, then we can use it in our template, so our App.vue file now looks like this:

<template>
    <div>
    <!-- Add the nav menu component to the page -->
    <nav-menu></nav-menu>
    <div class="ui center aligned padded grid">
        <div class="column">
            <div class="ui basic segment">
               <router-view></router-view>
            </div>
        </div>
    </div>
</div>
</template>

<script type="text/javascript">
import NavMenu from './core/NavMenu.vue'

export default {
  components:{
    NavMenu
  }
}
</script>

With the nav menu, the page is starting to look like a real website. Now let’s create a footer at components/core/SiteFooter.vue:

<template>
    <div class="ui inverted vertical footer segment" id="main-footer">
        <div class="ui center aligned container">
            <div class="ui stackable inverted divided grid">
                <div class="ten wide column">
                   <h4>About</h4>
                    <!-- Add about stuff here -->
                </div>      
                <div class="six wide column">
                    <h4 class="ui inverted header">Social</h4>
                    <div class="ui horizontal inverted large link list">
                        <a class="item" href="#"><i class="big twitter icon"></i></a>
                        <a class="item" href="#"><i class="big facebook icon"></i></a>
                        <a class="item" href="#"><i class="big github icon"></i></a>
                    </div>
                </div>
            </div>
            <div class="ui inverted section divider"></div>
            <div class="ui horizontal inverted small divided link list center aligned">
                <a class="item" href="#">Contact</a>
                <a class="item" href="#">Terms and Conditions</a>
                <a class="item" href="#">Privacy Policy</a>
            </div>
        </div>
    </div>
</template>


Once again, let's add it to `App.vue` by importing it and adding it to our components property. we also want to make sure the footer doesn't sit in the middle of the page if the content is too short, so we'll add a bit of JavaScript wizardry to force it down if that's the case, so our final base component now looks like this:


<template>
    <div>
        <div id="menu">
            <nav-menu></nav-menu>
        </div>

        <div id="content">
            <div class="ui center aligned padded grid" :style="{'min-height' : minHeight + 'px'}">
                <div class="column">
                    <div class="ui basic segment">
                        <router-view></router-view>
                    </div>
                </div>
            </div>
        </div>

        <div id="footer">
            <site-footer></site-footer>
        </div>
    </div>
</template>

<script type="text/javascript">
import NavMenu from './core/NavMenu.vue'
import SiteFooter from './core/SiteFooter.vue'
export default {
    components: {
        NavMenu,
        SiteFooter
    },
    created() {
        window.addEventListener('resize', () => {
            this.calculateMinHeight()
        });
    },
    mounted() {
        this.calculateMinHeight()
    },
    methods: {
        calculateMinHeight() {
            let menuHeight = document.getElementById('menu').clientHeight;
            let footerHeight = document.getElementById('footer').clientHeight;
            this.minHeight = window.innerHeight - menuHeight - footerHeight;
        }
    },
    data() {
        return {
            minHeight: 0
        }
    }
}
</script>

As you can see we've added some JavaScript to calculate the correct position of the footer when the component is mounted, we've also added a listener so we can re-position the footer when the browser is resized.

Creating the FrontPage Component

With our base component setup, we now want to create a front page component, obviously we don't have any posts yet, so we'll just mock up a layout. Let's create a new component called FrontPage.vue in a new folder called views at resources/assets/js/components/views.

I'm thinking that the front page should have some of the most recent blog posts, which we can layout using semamtic-ui's cards. Because we want to reuse our card we shoule create a component for it, so let's do that first. Create a new component called Card.vue in another new folder called "elements" at resources/assets/js/components/elements and now let's design our Card component.

<template>
    <div class="ui fluid card">
        <div class="content">
            <div class="left floated">Posted in
                <span  v-for="(category, index) in categories">
                <router-link :to="category.link">{{category.name}}</router-link><span v-if="!isLast(index)">, </span>
            </div>
        </span>

        </div>
        <div class="content">
            <div class="header">
                <router-link to="/">
                    <slot name="title"></slot>
                </router-link>
            </div>
            <div class="meta">Published
                <slot name="published"></slot>
            </div>
            <div class="description">
                <slot name="description">
                    </slot>
            </div>
        </div>
        <div class="extra content">
            <span class="right floated">
      <i class="heart outline like icon"></i>
      {{likes}} likes
    </span>
            <i class="comment icon"></i> {{comments}} comments
        </div>
    </div>
    </div>
</template>

<script type="text/javascript">
export default {
    props: {
        comments: {
            default: 0,
            type: Number
        },
        likes: {
            default: 0,
            type: Number
        },
        categories: {
            default: function() {
                return [{
                    link: "/",
                    name: "Unknown"
                }]
            }
        }
    },
    methods: {
        isLast(index){
           return this.categories.length-1 === index;
        }
    }
}
</script>

This component has a few slots, so we can add a title, description and published date. We've also added some props so we can pass the number of "likes" and "comments" for the post and also pass an array of objects for the categories. The isLast() method is used to ensure that we don't print a comma (,) after the last category which is used inside out v-for.

Now that we have a card component, let's add a few of them to our FrontPage.vue component we created earlier. I'm just going to take a few blog posts from here and add them manually, but in future we will retrieve our recent posts from our laravel api, here's the FrontPage.vue

<template>
    <div class="ui three doubling cards basic segment">
        <card :categories="[{link: '/', name: 'How To'}, {link: '/', name: 'In Depth'}]">
            <span slot="title">V-cloak: Dealing with load latency in Vue.js</span>
            <span slot="published">2 Days Ago</span>
            <p slot="description">If you’re using Vue as an HTML templating engine, i.e. you’re placing vue template items directly in your HTML, then you may have noticed that the raw template items display for a short time before the Vue instance loads, which can look pretty ugly.</p>
        </card>

        <!-- Cards removed for brevity -->

    </div>
</template>

<script type="text/javascript">
import Card from '../elements/Card.vue'
export default {
    components: {
        Card
    }
}
</script>

I've knocked a couple of cards out here, but you can find the complete FrontPage.vue file on the projects GitHib page

All we need to do now is add that to our app.js file in resources/assets/js/app.js. We can now delete the FrontPage render function we created earlier and replace it with or FrontPage component like so:

import FrontPage from '.components/views/FrontPage.vue'

Now if you open up your browser you see our app starting to take shape:

setup_complete_screengrab

There was quite a lot of work in this part, but we now have the basis of an app that can be adjusted for any purpose. The first four parts of this project are now available on github, and for convenience, you can also pull them directly from the scaffold branch to use this setup for any project.

In part 5 we are going to setup our Admin section.

Advertisements