Start to Finish: Let’s build Google’s Dictionary widget

Learn to use APIs, VueJS, SCSS, Promises, and write semantic HTML5

Robert Mion
Aug 13 · 20 min read
By the end of this tutorial series, you’ll have re-created this Dictionary widget

The steps we will take together

  1. Break down the widget to understand what HTML elements we should use
  2. Research, test and sign up for an API service that will get us the data we need
  3. Add mock data from the WordsAPI to our HTML template to validate its structure
  4. Use VueJS to quickly prototype our widget using data from the API
  5. Piece by piece, replace hard-coded text with data from our Vue model
  6. Test our app and update portions to account for data received from the API for any word
  7. Style our app using SCSS to mimic that of Google’s widget

Break down the widget to understand what HTML elements we should use

The structure I chose contains nested <DL>s, an <OL>, and a few other semantic elements

Here’s the breakdown

  • The widget has a heading, Dictionary
  • Then a short form: an input and submit button
  • The result of searching is a definition list with one dt and a dd for each set of nouns and verbs
  • Inside each of those dd s is an ol with list items for each noun or verb
  • Each li will contain the definition and may include the term used in a sentence and one or more synonyms. The definition is a p . The sentence is a q wrapped in a p and the synonym list is another dl.

Here is the HTML with content placeholders marked in {{ }}

<div id="app">
<h1>Dictionary</h1>
<input type="text" />
<button>Search</button>
<dl>
<dt>
<p>{{syllables}}</p>
<p>{{pronunciation}}</p>
</dt>
<dd>
<p>noun</p>
<ol>
<li>
<p>{{definition}}</p>
<p>
<q>{{sentence}}</q>
</p>
<dl>
<dt>synonyms</dt>
<dd>{{synonym}}</dd>
</dl>
</li>
</ol>
</dd>
<dd>
<p>verb</p>
<ol>
<li>
<p>{{definition}}</p>
<p>
<q>{{sentence}}</q>
</p>
<dl>
<dt>synonyms</dt>
<dd>{{synonym}}</dd>
</dl>
</li>
</ol>
</dd>
</dl>
</div>

Research, test and sign up for an API service that will get us the data we need

In the bottom-left corner of Google’s Dictionary widget there is a label, ‘From Oxford’.

This led me to google oxford dictionary api.

And thus, the official Oxford Dictionaries API.

Unfortunately, I discovered two bottlenecks:

  1. The API has an associated cost whenever it is called. I would rather not pay for only a handful of API calls.
  2. It does not appear that the API returns synonyms, which are a key part of the widget we are trying to re-create.

It’s very likely that I didn’t read enough on the website…and that what I perceive as bottlenecks are in fact not.

But when another google search for dictionary api features a plain english resource call WordsAPI with none of the bottlenecks above, it was easy to make my decision.

A screenshot of the WordsAPI homepage, scrolled to the ‘Try it’ section with the word ‘program’ entered and queried
WordsAPI is free as long as we don’t exceed 2,500 API requests within any 24 hour period

Use of the WordsAPI — and nearly all APIs, as a matter of fact — requires a key.

The key is used, among other reasons, to track the number of requests made in a day.

To get your WordsAPI key, you must:

  1. Sign up at RapidAPI, the umbrella service through which you can then subscribe to the WordsAPI
  2. Enter billing information…in case you exceed the Free tier’s limit and they must charge you
  3. Subscribe to the WordsAPI so that any requests you make to the API can be validated…and you get back real data instead of a pesky error message.
Visit rapidapi.com and sign up using one of the various methods
Once logged in, click on ‘My Apps’, then ‘Billing’ and ‘Billing Information’. Click ‘Edit’ and enter your credit card information. You will only be billed if you exceed 2,500 API requests to the WordsApi.
Go to the ‘API Marketplace’ tab. In the search field, type ‘wordsapi’ and select the first result
On the WordsAPI page, scroll down a bit until you see the blue button labeled ‘Subscribe to Test’. Click on it to subscribe to the WordsAPI.
To verify you have subscribed, return to the ‘My Apps’ tab, click on ‘Billing’, then ‘Subscriptions & Usage’ and confirm you see WordsAPI listed.

Add mock data from the WordsAPI to our HTML template to validate its structure

Recall the request we made for ‘program’ on the WordsAPI homepage ‘Try it’ section

The portions of interest of the response returned from that API request are represented below:

{
"word": "program",
"results": [
{
"definition": "an integrated course of academic studies",
"partOfSpeech": "noun",
"synonyms": [
"course of study",
"curriculum",
"programme",
"syllabus"
],
"examples": [
"he was admitted to a new program at the university"
]
},
// more result objects
],
"syllables": {
"count": 2,
"list": [
"pro",
"gram"
]
},
"pronunciation": {
"all": "'proʊgræm"
}
}

The question we must answer in this step is…have we setup our HTML template to effectively display each chunk of data shown above?

Here, again, is our template:

<div id="app">
<h1>Dictionary</h1>
<input type="text" />
<button>Search</button>
<dl>
<dt>
<p>{{syllables}}</p>
<p>{{pronunciation}}</p>
</dt>
<dd> <!-- will repeat for each result that is a noun -->
<p>noun</p>
<ol>
<li>
<p>{{definition}}</p>
<p>
<q>{{sentence}}</q> <!-- repeat for each example -->
</p>
<dl>
<dt>synonyms</dt>
<dd>{{synonym}}</dd> <!-- repeat for each synonym -->
</dl>
</li>
</ol>
</dd>
<dd> <!-- will repeat for each result that is a verb -->
<p>verb</p>
<ol>
<li>
<p>{{definition}}</p>
<p>
<q>{{sentence}}</q>
</p>
<dl>
<dt>synonyms</dt>
<dd>{{synonym}}</dd> <!-- repeat for each synonym -->
</dl>
</li>
</ol>
</dd>
</dl>
</div>
  • {{ syllables }} can be attained from the syllables.list array
  • {{ pronunciation }} is given to us from the same named property
  • {{ sentence }} can be attained from each result’s examples array
  • Each {{ synonym }} can be attained from the synonyms array
  • {{ definition }} is given to us from the same named property
  • And we can correctly group nouns and verbs based on the partOfSpeech property for each result

Our HTML template is ready. Let’s now create a Vue instance and work on displaying data from the response object in that template.

Use VueJS to quickly prototype our widget using data from the API

Let’s remember throughout this step that:

  • When prototyping, we want to work quick
  • Our goal is to make something work…for demonstration and learning purposes only
  • At this stage, we are not necessarily concerned with using best practices, writing production-ready code, or accounting for all edge cases

Given this understanding, I find that VueJS, a progressive JavaScript framework, empowers me to work fastest when prototyping widgets like this.

How to add VueJS to your project

Using a code editor with your HTML file loaded? Add a script tag just before the closing </body> tag with the src attribute set to one of the many CDNs that VueJS is made available, like this:

    ...
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js" />
</body>
</html>

Using a service like Codepen? From your ‘pen’, click the gear next to ‘JS’ and search for ‘vue’. It should auto-populate a list, the first of which is VueJS. Select that to add it to your pen, Save and Close.

Search for ‘vue’ inside the JavaScript tab of the ‘Pen Settings’ modal window
After selecting ‘Vue’ from the list of options, you will see a link to ‘cloudflare.com’ added in the first text box

How to start prototyping with VueJS

Vue is a constructor function: it’s a function…that constructs an object.

That means in order to use Vue, we must include the new keyword, a set of () parentheses, and save what is returned in a variable, like this.

var app = new Vue();

Like most functions, Vue expects one or more arguments when you call it.

For our purposes, we will pass in a single argument, an object.

var app = new Vue( {} );

This object will have several properties, each of which is something Vue expects.

Let’s start with the property that plugs Vue into our widget.

Recall that in our HTML template, I wrapped everything in a <div> with the id of app.

<div id="app">
...
</div>

I did that for Vue. Vue plugs in to a single HTML element. And it is easiest to use the id when targeting an element.

var app = new Vue({
el: "#app"
});

We have added a single key: value pair to the object we pass in to the Vue constructor function. The key is el, short for element, and the value is "#app", a string containing the CSS selector targeting our div.

And just like that, our div is now a Vue instance! Sadly, it doesn’t do anything yet. Let’s fix that.

How to add default state to our Vue instance

Let’s add another key: value pair to the object. This pair serves an important role in a Vue instance: it holds the state of our app. The key is called data and its value is an object, as shown below.

var app = new Vue({
el: "#app",
data: {}
});

Inside of this object we are free to add key: value pairs with whatever names we wish, as they are unique to our app and not dictated or expected by Vue.

We only need to store two pieces of data, the response from our API request, and the word we want to look up.

We need to set initial, default values for each item, so that Vue can properly register and track any future changes to each item.

var app = new Vue({
el: "#app",
data: {
response: null,
word: ""
}
});

I chose intuitive labels for each data object key, response for the API response, and word for the word we want to look up.

How to setup the WordsAPI request as part of our Vue instance

There are three parts to this step:

  • We need to send a “GET’ request to the WordsAPI that includes the word we want to look-up.
  • When we get a response from the API, we want to convert it to JSON so we can perform operations on it.
  • After converting it to JSON, we want to store the response object in the response property of the data object that we initialized with null as a value.

We will do all of this inside a function. That function will be stored in our Vue instance, because Vue is what will invoke it at the appropriate time.

Let’s start writing the function:

function handleAPIRequest() {
...
}

It’s a long function name, but it’s quite clear in what it does: it handles our API request.

A convenient, built-in way to send “GET” requests, is by way of the Fetch API.

The article, Using Fetch, on the Mozilla Developer Network, offers a helpful basic example of the Fetch API:

fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(JSON.stringify(myJson));
});

What’s happening here? Well, a lot.

  • fetch is a function that is being called with a single argument: a string containing a URL, in this case pointing to a JSON file
  • The next line is actually a continuation of the first: the fetch function call returns an object. We immediately call the then method (a.k.a. function), passing to it a single argument: a function.
  • That function — the one passed as an argument to the then function call — when called, expects a single parameter — in this case labeled response. The function executes a single command: return the result of calling json upon itself. This command effectively will convert a string of text sent by the API request into a JavaScript object that we prefer to work with.
  • The next line is yet another continuation of the fetch function call on line one. It works identical to the previous then function call. The function passed to it will expect a single parameter. It will receive the JSON object returned from the previous function call.
  • We will replace the console.log() command with one that stores the JSON object into the response property within our Vue instance’sdata object.

In more seemingly simple but advanced terms, Fetch returns a Promise. Each of the then function calls registers subsequent actions to be performed if and when the Promise successfully resolves.

How can we modify the code sample to meet our needs?

In four important ways:

  1. We need to send a request to a different URL
  2. We need to append a piece of data to that URL
  3. We need to configure that request before sending, so that WordsAPI can validate our request properly
  4. We need to store the eventual JSON back into our Vue instance
fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
headers: {
"X-Mashape-Key": "someCrazyLongAPIKeyThatContainsNumbersAndLetters"
}
})
.then(function(response) { return response.json() })
.then(function(myJson) { this.response = myJson })

Which URL to use?

We get this from WordsAPI. Their documentation states that when looking up a word, we use this URL: https://wordsapiv1.p.mashape.com/words/

How do we add our data to that URL?

That URL expects one last bit after the words/ part: the word we want to look-up. In our Vue instance, that value will be stored in the property labeled word inside the data object. Vue gives us a convenient way to refer to any value stored within the data object: we write this.word instead of this.data.word or having to access word through some other means.

We can use a template literal to clearly write the full URL, including our data. Template literals are surrounded by back-ticks: the bottom symbol on the key to the left of the 1 on most keyboards.

Template literals allow us to insert evaluated data into a string using this syntax: ${ expression }.

Therefore, the string we pass to fetch will look like this:

fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`)

How do we send the required API key with our request so WordsAPI doesn’t return an error about invalid access?

The fetch function call accepts a second parameter: an object used to configure the API request.

fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {})

This object allows for a multitude of possible key: value pairs. We only need to set one, headers.

fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
headers: {}
})

The WordsAPI requires us to add a single key: value pair to the headers object. The key is X-Mashape-Key and the value is a long string of characters that is assigned to your account when you subscribed to the WordsAPI.

Highlighted in red is the key you’ll need for the API request. As long as you’re logged in, use this link to jump to this page and copy-paste your key
fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
headers: {
"X-Mashape-Key": "yourAPIKeyThatContainsNumbersAndLetters"
}
})

Placing this API call within the context of our function now looks like this:

function handleAPIRequest() {
fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
headers: {
"X-Mashape-Key": "yourAPIKeyThatContainsNumbersAndLetters"
}
})
}

Recall that the MDN sample code featured two chained calls to then:

fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(JSON.stringify(myJson));
});

We can copy the first then call exactly as written.

function handleAPIRequest() {
fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
headers: {
"X-Mashape-Key": "yourAPIKeyThatContainsNumbersAndLetters"
}
})
.then(function(response) {
return response.json();
})
}

We need to modify the second then call to store the JSON back to our Vue instance’s response property.

function handleAPIRequest() {
fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
headers: {
"X-Mashape-Key": "yourAPIKeyThatContainsNumbersAndLetters"
}
})
.then(function(response) {
return response.json();
})
.then(function(body) {
this.response = body;
})
}

Just like with word, we can conveniently refer to any properties stored inside of Vue’s data object via this. and then the name of the property, instead of having to insert this.data. or by some other means.

If you prefer to use JavaScript’s newer arrow => functions, your handleAPIRequest function will look like this:

function handleAPIRequest() {
fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
headers: {
"X-Mashape-Key": "yourAPIKeyThatContainsNumbersAndLetters"
}
})
.then(response => response.json())
.then(body => this.response = body)
}

Where do we put this function within Vue?

You currently have two key: value pairs on your Vue configuration object: el and data. Let’s add another one that Vue recognizes: methods. It’s value is an object that stores one or more functions.

var app = new Vue({
el: "#app",
data: {
response: null,
word: ""
},
methods: {}
});

Our handleAPIRequest function can be one of this object’s property’s values, like this:

var app = new Vue({
el: "#app",
data: {
response: null,
word: ""
},
methods: {
handleAPIRequest: function () {
fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
headers: {
"X-Mashape-Key": "yourAPIKey"
}
})
.then(response => response.json())
.then(body => this.response = body)
}
}
});

How do we trigger the API request with any word we want?

Let’s return to a portion of our HTML template:

<div id="app>
<h1>Dictionary</h1>
<input type="text" />
<button>Search</button>
...
</div>

We need to do two things:

  1. Bind the input to the word property in our data object
  2. Add an event listener to the button such that when clicked, the handleAPIRequest function is invoked

Bind the input to the word property in our data object

When you enter text in the input field, that text should be saved in your Vue’s data object as the value re-assigned to the word property. In addition, whatever is stored in word should always appear as the value of the input field.

This is called two-way data binding, and Vue offers a highly convenient syntax for creating it: the v-model directive.

Vue directives are conveniently written as faux HTML attributes and prefixed with a v-.

Vue’s model directive, like many others, is written like this:

<input v-model="word" type="text" />

What goes inside the quotation marks is any valid JavaScript expression (a.k.a. anything you would normally write on the right-hand side of the assignment operator, =)

We only need to place the four-character string word between the quotation marks, as shown above. The beginning of our HTML template now looks like this:

<div id="app>
<h1>Dictionary</h1>
<input v-model="word" type="text" />
<button>Search</button>
...
</div>

Add an event listener to the button such that when clicked, the handleAPIRequest function is invoked

Vue has a directive meant for listening to DOM events: on.

Its syntax is a bit odd at first.

<button v-on:click="handleAPIRequest">Search</button>
  • It is prefixed with v-
  • Then the directive name, on
  • Then a colon, which denotes that what follows is an argument (much like calling a function)
  • Then the name of the argument. In this case, it must be a valid DOM event, like click.
  • Then the usual equals sign, opening quotation and closing quotation
  • Between the quotes goes the JavaScript expression. In this case, we enter the name of the function that should be invoked. Look closely: we are not invoking the function (there are no parentheses).

Our template now looks like this:

<div id="app>
<h1>Dictionary</h1>
<input v-model="word" type="text" />
<button v-on:click="handleAPIRequest">Search</button>
...
</div>

How to display the various parts of the response object in our template

Referring back to our template, there are several items that we need to display, and each of them will come from the JSON returned from our API request:

  • {{ syllables }}
  • {{ pronunciation }}
  • {{ definition }} {{ sentences }} and {{ synonyms }} for each noun or verb

Vue offers two ways to store data, one dependent on the other:

  • The data object stores all ‘raw’ data
  • A computed object can store data that is somehow derived from values stored in data

For example, data may store the array, [1, 2, 3, 4, 5]. If, somewhere in my template I wanted to display the odd numbers that are a subset of that array [1, 3, 5], I would utilize a computed property that is a filtered copy of that array.

In our case, each of the labels above that we want to display in our template can be derived from the JSON object we will store in response within the data object. Therefore, we will create computed properties for each one. Each computed property will essentially store values plucked from response.

Let’s start with ‘syllables’ to get the hang of things

Remember our goal:

  • Translate data from one structure to another — from how it is structured by the API’s response…to how we want to present it in our template

For syllables, we are given an array:

"syllables": {
"count": 2,
"list": [
"pro",
"gram"
]
},

…and we want our template to display pro•gram.

  • We must go from an array, list, to a string
  • The elements of the array will be concatenated (a.k.a. combined) with the bullet • as the glue

list is a property of the parent object syllables which is a property of the parent object that was returned by our API request and stored in our Vue instance’s data object’s response property.

All that to say…this is how we would access list inside our Vue instance:

this.response.syllables.list

All arrays in JavaScript come with access to a function called join. When invoked, it merges each element in the array using the character passed in as the function’s argument. This is just the function we need to achieve our goal:

this.response.syllables.list.join("•")
// "pro•gram"

We know how to perform our translation. Now, let’s place this command inside of Vue as a computed property.

Recall the shape of our Vue instance thus far:

var app = new Vue({
el: "#app",
data: {...},
methods: {
handleAPIRequest: function() {...}
}
});

Vue’s computed property works very similar to methods: it stores an object whose properties all point to functions.

Let’s add computed and its first key: value pair to store syllables:

var app = new Vue({
el: "#app",
data: {...},
methods: {
handleAPIRequest: function() {...}
},
computed: {
syllables: function() {
return this.response.syllables.list.join("•");
}
}
});

There’s one caveat we must account for

Before we look up a word, this.response will be its default value, null.

JavaScript will therefore throw an error when we attempt to access this.response.syllables because null.syllables does not exist.

We need to control JavaScript’s flow:

  • If this.response is null, return…say…an empty array (really, any empty object will do)
  • Otherwise (a.k.a. when this.response is the returned JSON object), return this.response.syllables.list.join("•")

To accomplish this in a succinct manner, we will use JavaScript’s ternary operator, which takes this pseudo-form:

(is this true) ? [yes: do this] : [no: do this instead]

In other, shorter words:

condition ? true : false

Our updated syllables function looks like this:

var app = new Vue({
el: "#app",
data: {...},
methods: {
handleAPIRequest: function() {...}
},
computed: {
syllables: function() {
return this.response === null ? [] : this.response.syllables.list.join("•");
}
}
});

If this.response is null, return an empty array. Otherwise, return our join function call.

As a sort of fast-forward, below are all four computed properties and their corresponding function values:

var app = new Vue({
el: "#app",
data: {...},
methods: {
handleAPIRequest: function() {...}
},
computed: {
syllables: function() {
return this.response === null ? [] : this.response.syllables.list.join("•");
},
pronunciation: function() {
return this.response === null ? [] : `/${this.response.pronunciation.all}/`
},
nouns: function() {
return this.response === null ? [] : this.response.results.filter(result => result.partOfSpeech === "noun"
},
verbs: function() {
return this.response === null ? [] : this.response.results.filter(result => result.partOfSpeech === "verb"
}
}
});
  • Pronunciation comes formatted as a string attached to pronunciation.all. We are adding / characters to the beginning and end for presentation purposes.
  • nouns and verbs are derived by filtering the results array within the response to respectively include only results whose partOfSpeech property is either the string noun or verb. Both will return arrays

Displaying all this data in our template…finally!

To display these computed properties in our template, we first need to review two more Vue directives: v-if and v-for.

  • v-if will evaluate an expression. If true, the DOM element and all its children will be added to component. If false, the DOM element and all its children will be removed from the component.
  • v-for will iterate over an object or array, producing DOM elements for each key-value pair or element.

Let’s start with our parent-most <dl>. It should only appear when a user has entered a word, pressed the button, and the API returns a response with definition data. In other words, if a user hasn’t looked up a word, or the word is not valid with regard to the API, then we should not see a <dl> rendered to the page or in the DOM.

Therefore, the opening <dl> tag now looks like this:

<dl v-if="response">

If response is null, that expression will evaluate to false. If response is the JSON object, it will evaluate to true.

Next up, the two <dd>s that will potentially contain the list of definitions for a word, categorized into nouns and verbs.

Per our computed properties, both values will be arrays no matter what response is. The only difference is whether each one is empty or not.

Therefore, our v-if directives can check the array’s length to determine whether the <dd> renders (length > 0) or not (length == 0).

<dd v-if="nouns.length">
...
</dd>
<dd v-if="verbs.length">
...
</dd>

Within a given result, there are three things that could contain 0, 1 or more elements:

  • The amount of definitions
  • The amount of example sentences
  • The amount of synonyms

For each of these potential collections of items, we will leverage Vue’s v-for directive to ‘stamp out’ a DOM element per item.

Here is the syntax of Vue’s v-for directive in the context of a list item:

<li v-for="item in collection">...</li>

Inside of the <li> we can use item to refer to the current element in the iteration.

Recalling the portion of our template from earlier, we can now add the appropriate v-for directives to the markup:

    <dd v-if="nouns.length">
<p>noun</p>
<ol>
<li v-for="noun in nouns">
<p>{{noun.definition}}</p>
<p v-for="sentence in noun.examples">
<q>{{noun.sentence}}</q>
</p>
<dl>
<dt>synonyms</dt>
<dd v-for="synonym in synonyms">{{synonym}}</dd>
</dl>
</li>
</ol>
</dd>
  • An <li> for each result
  • A <p> and <q> for each example sentence
  • A <dd> for each synonym

Test our app and update portions to account for data received from the API for any word

Alas! You should now have the markup, script and API keys necessary to test you widget.

HTML

<div id="app">
<h1>Dictionary</h1>
<input type="text" v-model="word" />
<button @click="lookup">Look-up</button>
<dl v-if="response">
<dt>
<p class="syllables">{{syllables}}</p>
<p class="pronunciation">{{pronunciation}}</p>
</dt>
<dd v-if="nouns.length">
<p class="part-of-speech">noun</p>
<ol>
<li class="definition-group" v-for="noun in nouns">
<p>{{noun.definition}}</p>
<p v-for="example in noun.examples">
<q class="example">{{example}}</q>
</p>
<dl>
<dt class="synonym-heading">synonyms</dt>
<dd class="synonym" v-for="synonym in noun.synonyms">
{{synonym}}
</dd>
</dl>
</li>
</ol>
</dd>
<dd v-if="verbs.length">
<p class="part-of-speech">verb</p>
<ol>
<li class="definition-group" v-for="verb in verbs">
<p>{{verb.definition}}</p>
<p v-for="example in verb.examples">
<q class="example">{{example}}</q>
</p>
<dl>
<dt class="synonym-heading">synonyms</dt>
<dd class="synonym" v-for="synonym in verb.synonyms">
{{synonym}}
</dd>
</dl>
</li>
</ol>
</dd>
</dl>
</div>

JavaScript

var vm = new Vue({
el: "#app",
data: {
response: null,
word: "program"
},
computed: {
syllables() {
return this.response !== null ? this.response.syllables.list.join("•") : [];
},
pronunciation() {
return this.response !== null ? `/${this.response.pronunciation.all}/` : [];
},
verbs() {
return this.response !== null ? this.response.results.filter(result => result.partOfSpeech === "verb") : [];
},
nouns() {
return this.response !== null ? this.response.results.filter(result => result.partOfSpeech === "noun") : [];
},
},
methods: {
lookup() {
fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
headers: {
"X-Mashape-Key": "yourAPIKeyHere"
}
})
.then(response => response.json())
.then(body => this.response = body)
},
}
})
It lacks styling, but it works as expected!

There are two features missing from our code, that I’ll leave for your homework

  1. If a word has no synonyms, our widget still displays the word, synonyms, when it should not
  2. In Google’s version, many synonyms double as hyperlinks to look-up that the definition for that word. Not all are, though. How might we achieve this same effect and behavior?

And if you’re feeling ambitious, study Vue’s component-based architecture and refactor this single-component app into one consisting of several components.

Lastly, let’s style our app using SCSS to mimic that of Google’s widget

In the code snippet towards the end of this section, you will see the & character a few times. This special character enables an author to effectively nest styles of elements that are children of another inside that element’s style declaration block, while still referring to that parent element anywhere in the child selector.

For example:

.synonym-heading {
font-style: italic;

&::after {
content: ":";
}
}

would look like this in regular CSS:

.synonym-heading {
font-style: italic;
}
.synonym-heading::after {
content: ":";
}

Thus, the & in that context gets compiled to .synonym-heading when that selector block is interpreted.

SCSS

#app {
min-width: 50vw;
max-width: 600px;
margin: 2em auto;
font-family: sans-serif;
border: 1px solid lightgray;
border-radius: 10px;
padding: 1em;

h1 {
font-weight: normal;
font-size: 2em;
line-height: 1;
margin-top: 1em;
}

input, button {
font-size: 1.5em;
color: gray;
}

.syllables {
font-size: 2em;
margin: 0;
line-height: 1;
}

.pronunciation {
color: gray;
}

.synonym-heading {
font-style: italic;

&::after {
content: ":"
}
}

.synonym {
&:not(:last-child)::after {
content: ", ";
}
}

.synonym-heading,
.synonym {
color: gray;
margin: 0;
display: inline;
}

.part-of-speech {
font-style: italic;
}

.definition-group {
margin-bottom: 1em;
}

.definition-item {
margin: 0;
}

.example {
color: gray;
}
}

You reached the end…either through grit, or by way of scrolling the page.

Either way, thank you for reading. I hope you followed along, because doing so will make you a much stronger front-end developer.

If you enjoy my teaching style, please consider my mentorship and tutoring as you continue your journey to master front-end development.

Robert Mion

Written by

Designer. That’s Mitzi. Stop by tomorrow.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade