Programming

How to Create a Simple Descriptor Generator with Vue

Some time ago, the company I’m still working at needed a lot of descriptor¬†names. Like, Support Specialists, 24/7 Help, etc. The goal of this article is to show you how simple it is to create a generator tool using Vue. To get a better idea of how simple it is, look below. Also, here is the final product you can play with before reading the article.

Descriptor Generator

What do we need?

I’m using Vue Cli for this project since it’s simple to set it up using the command vue¬†create descriptor-generator.¬†Bulma as a CSS framework seems nice, let’s have that too: yarn add bulma. That’s it? Yep, that’s it. It’s so simple, we don’t need to install the world to get this project done. Oh, we’ll also take advantage of LocalStorage but that’s already built in.

Although it’s a very basic setup, I still wanted to change the favicon. You can do that by updating the file in the public folder. It seems like we are ready to tackle the programming parts.

Code Dump and a Discussion

I figured there is no need to break this application into different components. We have two text areas where we can enter the adjectives and nouns, labelled as bits and pieces. We then have two buttons: one for swapping the content of the two text areas from earlier if you decide to put content that makes sense to swap; and another one that actually generates the descriptor which stays disabled until conditions are ripe.

Therefore the layout is a basic header followed by a container with two sections each holding two columns. Since Bulma is flex based and the columns will do their thing in mobile devices, our CSS will be minimal; mainly to add a few colours and alter some spacing. Without further ado, I’m sharing the whole code with the discussion followed after.

<template>
  <main id="app">
    <div class="section">
      <h1 class="title is-4 has-text-centered">Descriptor Generator</h1>
    </div>
    <div class="container">
      <div class="section">
        <div class="columns">
          <div class="column">
            <div class="content">
              <h2 class="subtitle is-5">Instructions</h2>
              <ul>
                <li>
                  Fill fields with text by pressing
                  <span class="tag is-dark">Return</span> to separate entries
                </li>
                <li>Alternatively, swap the content of the fields</li>
              </ul>
            </div>
          </div>
          <div class="column is-half-mobile is-two-fifths-tablet is-one-third-desktop">
            <div class="level">
              <div class="level-left">
                <button class="button is-info is-fullwidth" @click="swap">
                  Swap Bits & Pieces
                </button>
              </div>
              <div class="level-right">
                <button class="button is-primary is-fullwidth" @click="generate" :disabled="bits.length === 0 || pieces.length===0">
                  Generate Descriptor
                </button>
              </div>
            </div>
            <div class="notification" :class="{hidden: bits_and_pieces.length===0}">
              {{bits_and_pieces}}
            </div>
          </div>
        </div>
      </div>
      <div class="section">
        <div class="columns">
          <div class="column">
            <label class="label">Bits</label>
            <textarea class="textarea" rows="20" v-model="bits" placeholder="put your bits here, hit enter between each entry" />
          </div>
          <div class="column">
            <label class="label">Pieces</label>
            <textarea class="textarea" rows="20" v-model="pieces" placeholder="put your pieces here, hit enter between each entry" />
          </div>
        </div>
      </div>
    </div>
  </main>
</template>

<script>
export default {
  name: "app",
  data() {
    return {
      bits: localStorage.getItem("descriptorBits") || "",
      pieces: localStorage.getItem("descriptorPieces") || "",
      bits_and_pieces: ""
    };
  },
  watch: {
    bits: value => {
      localStorage.setItem("descriptorBits", value);
    },
    pieces: value => {
      localStorage.setItem("descriptorPieces", value);
    }
  },
  methods: {
    random(limit) {
      return Math.floor(Math.random() * limit);
    },
    generate() {
      let bits = this.bits.split("\n").filter(entry => entry !== "");
      let pieces = this.pieces.split("\n").filter(entry => entry !== "");

      this.bits_and_pieces = `${bits[this.random(bits.length)]} ${
        pieces[this.random(pieces.length)]
      }`;
    },
    swap() {
      const bits = this.bits;
      const pieces = this.pieces;

      this.pieces = bits;
      this.bits = pieces;
    }
  }
};
</script>

<style lang="scss">
main > .section {
  background: #222;
}

main .title {
  color: #ddd;
}

.hidden {
  display: none !important;
}

div .section {
  padding-top: 1.5em;
  padding-bottom: 1.5em;
}

div .notification {
  padding: 0.75em;
}

.button {
  margin-left: 0.25em;
  margin-right: 0.25em;
}

@media screen and (max-width: 480px) {
  .level {
    width: max-content;
  }
}
</style>

Let’s start with the data section where we check for initial conditions. You might have bits and pieces stored in LocalStorage from a previous session. If that’s the case, we take those values over instead of starting with a blank slate.¬†bits_and_pieces is what will be used as the generated descriptor so that should start blank.

data() {
  return {
    bits: localStorage.getItem("descriptorBits") || "",
    pieces: localStorage.getItem("descriptorPieces") || "",
    bits_and_pieces: ""
  };
},

So, how do we then update the LocalStorage values? That’s what the watch block does. It watches the properties in the data section and updates LocalStorage accordingly with the new value. If you compare this Vue branch of the application with the master branch (built with React originally) on Github, you’ll see this watch part to be the main difference. What I mean is the way to handle such updates depends on the¬†architectural differences between Vue and React.

watch: {
  bits: value => {
    localStorage.setItem("descriptorBits", value);
  },
  pieces: value => {
    localStorage.setItem("descriptorPieces", value);
  }
},

Methods to this Madness?

Let’s now look at the methods this application is using. For both buttons, swap and generate, we have a method defined. Additionally, we have a method called random that is not bound to any event in the DOM but used within the generate method. This random method is generating a random integer between 0 and limit - 1 where limit is the total number of available bits or pieces.

The application is encouraging people to separate the entries with the Return character. Hence, the reason to split the entries by “\n”. However, we should also filter out empty lines. Once you have an array of bits and pieces (lines 79:80), you can then call the random method to populate bits_and_pieces.

generate() {
  let bits = this.bits.split("\n").filter(entry => entry !== "");
  let pieces = this.pieces.split("\n").filter(entry => entry !== "");

  this.bits_and_pieces = `${bits[this.random(bits.length)]} ${pieces[this.random(pieces.length)]}`;
},

Swap method is simply swapping the values of bits and pieces properties by storing their value in temporary variables. Since we are watching those two properties. the LocalStorage will be updated automatically. You might be wondering how bits and pieces properties are updated in the first place. Look at line 44 and 48 where v:model is taking care of it.

<textarea class="textarea" rows="20" v-model="bits" placeholder="put your bits here, hit enter between each entry" />
...
...
...
<textarea class="textarea" rows="20" v-model="pieces" placeholder="put your pieces here, hit enter between each entry" />

 

Final Notes

I actually only recently built this project with Vue. Initially, a year ago or so, it was built with React. So, why bother to rebuild it? I was adding a few cosmetic changes to the React version but then I also wanted to see how the conversion would go. You can check out both branches at my Github page.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.