Vue.js Version of My Random Comic Book Viewer

A little over six months ago I blogged ("Serverless Demo - Random Comic Book Character via Comic Vine API") an example of a simple front end to a serverless function I built that returned a random comic book character from the Comic Vine API. I still feel a bit sketchy about using the API. The forum is "alive" but no one official seems to be minding the store so to speak. That being said, I thought it would be fun to rebuild the demo in Vue.js.

If you don't want to go back and read the original article, let me quickly go over how it worked.

First, I had a simple serverless function running in OpenWhisk to get the random comic character via the API. You can find the source code for that API here. The front end is what we care about. The HTML was essentially nothing as nearly all the displayed content was generated by JavaScript. I used jQuery to hit my API and then generated multiple different strings.

I used template literals to make that code a bit nicer. So instead of a bunch of string manipulations, I had stuff like this:

if(char.creators.length) {
    creatorsTemplate = '<h2>Creators</h2><ul>';
    char.creators.forEach((creator) => {
        creatorsTemplate += `<li><a href="${creator.site_detail_url}" target="_new">${creator.name}</a></li>`;
    });
    creatorsTemplate += '</ul>';
} 

let mainTemplate = `
<h1>${char.name}</h1>
<p>
    <strong>Publisher:</strong> ${publisher}<br/>
    <strong>First Issue:</strong> <a href="${char.first_issue.site_detail_url}" target="_new">${char.first_issue.volume.name} ${char.first_issue.issue_number} (${char.first_issue.cover_date})</a><br/>
</p>

<a href="${char.site_detail_url}" target="_new"><img class="heroImage" src="${image}"></a>
<p>${char.description}</p>

${creatorsTemplate}
${powersTemplate}
${teamsTemplate}
${friendsTemplate}
${enemiesTemplate}
            `;

There's more code involved then this, but this gives you the basic idea. I'm still generating strings by hand a bit (the first portion), but the template literals do make it nicer (second portion). However, with Vue, I should be able to do a lot of this in the HTML itself. Let's take a look at that.

First, the HTML:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Vue.js Random Comic Book</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://fonts.googleapis.com/css?family=Bangers" rel="stylesheet">
    <link rel="stylesheet" type="text/css" media="screen" href="app.css" />

</head>
<body>

    <div id="app" v-cloak>
        <div v-if="loading"><i>Loading Random Character...</i></div>
        <div v-else>
            <h1>{{char.name}}</h1>

            <p>
                <span v-if="char.publisher && char.publisher.name">
                    <strong>Publisher:</strong> {{char.publisher.name}}<br/>
                </span>
                <strong>First Issue:</strong> <a :href="char.first_issue.site_detail_url" target="_new">
                    {{char.first_issue.volume.name}} {{char.first_issue.issue_number}} ({{char.first_issue.cover_date}})</a><br/>
            </p>

            <a :href="char.site_detail_url" target="_new"><img class="heroImage" :src="char.image"></a>
            <p v-html="char.description"></p>

            <div v-if="char.creators.length">
            <h2>Creators</h2>
            <ul>
                <li v-for="creator in char.creators">
                    <a :href="creator.site_detail_url" target="_new">{{creator.name}}</a>
                </li>
            </ul>
            </div>

            <div v-if="char.powers.length">
                <h2>Powers</h2>
                <ul>
                    <li v-for="power in char.powers">{{power.name}}</li>
                </ul>
            </div>

            <div v-if="char.teams.length">
                <h2>Teams</h2>
                <ul>
                    <li v-for="team in char.teams"><a :href="team.site_detail_url" target="_new">{{team.name}}</a></li>
                </ul>
            </div>

            <div v-if="char.character_friends.length">
                <h2>Friends</h2>
                <ul>
                    <li v-for="friend in char.character_friends"><a :href="friend.site_detail_url" target="_new">{{friend.name}}</a></li>
                </ul>
            </div>

            <div v-if="char.character_enemies.length">
                <h2>Enemies</h2>
                <ul>
                    <li v-for="enemy in char.character_enemies"><a :href="enemy.site_detail_url" target="_new">{{enemy.name}}</a></li>
                </ul>
            </div>

            <p>
                    All data from <a href="https://comicvine.gamespot.com" target="_new">ComicVine</a>. 
                    <a @click="getCharacter" class="loadMore">Show me another!</a>
            </p>

        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script src="app.js"></script>
</body>
</html>

Now let's look at the JavaScript:

const api = 'https://openwhisk.ng.bluemix.net/api/v1/web/rcamden%40us.ibm.com_My%20Space/comicvine/randomCharacter.json';

const defaultMaleImage = 'https://comicvine.gamespot.com/api/image/scale_large/1-male-good-large.jpg';

const app = new Vue({
    el:'#app',
    data() {
        return {
            loading:false,
            char:null
        }
    },
    created() {
        console.log('run created');
        this.getCharacter();
    },
    methods:{
        getCharacter() {
            this.loading = true;
            console.log('getCharacter');
            fetch(api)
            .then(res => res.json())
            .then(res => {
                console.log('got data',JSON.stringify(res.character,null,'\t'));
                this.loading = false;

                //Start formatting the data

                /*
                If no description, copy deck over. deck can be blank too though
                also sometimes its <br/>, sometimes <p>.</p>
                */
                if(res.character.description && (res.character.description === '<br/>' || res.character.description === '<p>.</p>')) delete res.character.description;
                if(!res.character.description && !res.character.deck) {
                    res.character.description = 'No description.';
                } else if(!res.character.description) {
                    res.character.description = res.character.deck;
                }

                let image = '';
                if(!res.character.image) {
                    image = defaultMaleImage;
                } else if(res.character.image && !res.character.image.super_url) {
                    image = defaultMaleImage;
                } else {
                    image = res.character.image.super_url;
                }
                res.character.image = image;
                
                this.char = res.character;

            });
        }
    }
})

Ok, let's talk about it. First off, the JavaScript is somewhat simpler than the first version. Now that Vue is handling the layout aspects, the amount of JavaScript I need is quite a bit smaller. Since I can do some basic logic in HTML as well, conditionals and looping, I don't need to do as much in JavaScript. There are still a few cases where it makes sense to do such things in JavaScript. If you look at the section handling description, you can see it is a bit complex. I could do that in Vue's template syntax but it would be too ugly. I also handle the image aspect there too since it lets me nicely handle falling back to a default image.

I absolutely love though that my layout is now almost entirely an HTML concern, which feels intrinsically right to me. Once I had done the initial version, it became easier to make it even nicer than the original version. I included a simple link to do a reload and fetch another character. I could have done this before, but I was much more inclined to do so in the Vue version.

If you want to play with this yourself, you can do so here: https://cfjedimaster.github.io/webdemos/vuerandomcomicbook/

Note that if too many people visit the demo at once (hah), I'll hit the API limits. If you want to see the full source code for this demo, you can find it here: https://github.com/cfjedimaster/webdemos/tree/master/vuerandomcomicbook

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