Yet to take the code challenge #8 to build an animated image search? You can still do so! Send in your solutions using the comment section under the post, via Twitter with the hashtag #scotchchallenge or via the Slack channel #codechallenge in the Slack group.

In this post, we shall be solving the challenge by building an animated image search using Vue.js. While images are sourced from Pixabay, simple CSS animation and an amazing technique are employed to move the search bar to the top of the screen once the images load.

As an MVVM framework, Vue was chosen as DOM manipulation is pretty seamless also utilizing it's lifecycle methods are quite straightforward to grasp.

The Base

For this challenge, the base code comprising of HTML, CSS, and JavaScript was provided. This base script simply provides the base structure of the document and required styling.

The HTML

The HTML document created consists of markup for the search form and the empty section to display the images once loaded. Bulma classes were employed to style various elements of the document including the input area and the search button.

<div class="hero is-fullheight is-bold is-link" id="app">

  <!-- SEARCH FORM -->
  <form id="search-form">
    <input 
      type="text" 
      class="input is-medium"
      placeholder="What images would you like to see on Pixabay?">

    <button 
      type="submit" 
      class="button is-link is-medium">
        Search
    </button>
  </form>  

  <div class="hero-body">
  <div class="container">

    <!-- SEARCH RESULTS GO HERE-->

</div>
</div>
</div>

The CSS

The Sass variant of CSS was used to style the page. This styling is simply to define the color scheme of the page as well as dimensions of elements including the input element.

#search-form  {
  padding-left: 20%;
  padding-right: 20%;
  background: #363636;
  min-height: 80px;

  height: 100vh;
  display: flex;
  align-items: center;   

  input {
    margin-right: 5px;
  }
}

The JavaScript

For this challenge, the use of an API to fetch the images to be displayed is required and an API endpoint was gotten from Pixabay with which all image requests will be made. Also, a sample request URL is shown in the JavaScript code.

const apiUrl = 'https://pixabay.com/api'
const apiKey =  '8653965-67fc8570b61c58e735d9adade'
const searchQuery = 'dog'

const sampleRequestUrl = `${apiUrl}/?key=${apiKey}&q=${searchQuery}s&image_type=photo&per_page=15&safesearch=true`;

GET requests will be made to this API endpoint to receive queried images.

The Technique

In this challenge, Vue will be used to request for the images and pass these images to DOM. Moving the search box to the top of the document during a search is done by reducing the height of the search form. CSS animation is used to animate this reduction in size and it appears as though the search box is moved to the top of the viewport.

Create Vue Instance and State Data

In the JavaScript file, we create a new Vue instance which will be mounted using the el property on the DOM element with the ID of app.

new Vue({
  el: '#app',
  data() {
    return {
      apiUrl: 'https://pixabay.com/api',
      apiKey: '8653965-67fc8570b61c58e735d9adade',
      images: null,
      isSearching: false,
      query: ''
    }
  },
}

Once the Vue instance is created, the el property is used to mount the instance and data method is created which returns an object. In the data object returned, we pass the apiUrl as well as the apiKey and their respective values in the object. State data created are images, isSearching, and query. The images variable is to house the images returned from the search, isSearching is used to save the state of the app which could either be searching for images or not, these are represented by true and false respectively. The query property contains the search query.

Create search method

Vue's methods property is used to specify the search method, this method will be called once the search button is clicked. Axios, a promise based HTTP client is used to make API calls to the specified endpoint. In the Vue instance, we add the methods property just after the data function with:

[...]
methods: {
    search() {
      if (this.query) {
        this.images       = [];
        this.isSearching  = true;
        const searchQuery = encodeURIComponent(this.query);

        axios.get(`${this.apiUrl}/?key=${this.apiKey}&q=${searchQuery}s&image_type=photo&per_page=15&safesearch=true`)
          .then(res => {
            if (res.data.total != 0) {
              this.images      = res.data.hits
              this.isSearching = false;
            } else {
              this.isSearching = false;
            }          
          })
      }
    }
  }

From the script above we see that once search() method is called, an if statement verifies that query holds value. The images property is assigned an empty array, the value of isSearching is set to true and the encodeURIComponent method is used to encode the query, incase there are special characters in the query string.

Axios is used to make a GET request to the API endpoint and on the resolution of the promise, an if statement is used to verify the number if images returned as seen in the total property of the response.

If images are found, the array of images returned is assigned to the images property and the isSearching property is set to false to signify the end of the search operation. If no images were returned we simply change the state of the isSearching property to false.

Next up, shall proceed to pass these data to the DOM.

Pass Vue Data to the DOM

In passing the data to the DOM, the two areas of concern are the search form and the search result sections. In the search form, the v-model directive is used to establish a two-way data bind of the input field to the query property. The v-on directive is used to listen to the submit event on the form which in turn calls the search() method and also, the class of the form is set dynamically using the state of the images property.

Lastly, the button text is set dynamically and conditionally using Vue and ternary operators. For the search form we now have:

<!-- SEARCH FORM -->
  <form id="search-form" @submit.prevent="search" :class="{ 'has-searched': images }">
    <input 
      type="text" 
      class="input is-medium"
      placeholder="What images would you like to see on Pixabay?"
      v-model="query">

    <button 
      type="submit" 
      class="button is-link is-medium">
        {{ isSearching ? 'Searching' : 'Search' }}
    </button>
  </form>

In the search results section, a div is created once the images property holds data. In this div, the list rendering v-for directive is used to render each image in the array of fetched images. To render each image, the src property is set dynamically using the image URL fetched. Lastly, a default status text is rendered conditionally if no images were returned after the search was completed. For the search results section, we now have.

  <!-- SEARCH RESULTS -->
<div v-if="images">
  <div class="images columns is-multiline" v-if="images.length">
    <div class="column is-4" v-for="image in images">
      <img class="image" :src="image.webformatURL">
    </div>
  </div>

  <h3 class="title has-text-centered is-1" v-if="!images.length && !isSearching">? No results found!</h3>
</div>

At this point, the search system works but the results are not visible, how do we make them visible? Using CSS, we add height properties to the has-searched class which is assigned the search form once images are loaded. Creating the class style we have:

#search-form.has-searched     {
  height: 10vh;
}

At this point we are able to see the search results, but it still doesn't look nice with the instant movement of the search box to the top of the viewport. We fix this by introducing the CSS transition property in the .search-form ID style. The transition property is edited to:

transition: 1s cubic-bezier(.7,.28,.47,1.15) height;

With this, we have completed the challenge and everything works smoothly. You can see the final product here. https://codepen.io/Chuloo/pen/pVzYzL

Conclusion

In this post, we solved the challenge by building an animated search using Vue.js. CSS animation, as well as positioning techniques were employed to displace the search bar to the top of the viewport. Feel free to leave your comments and feedback under this post and let's look forward to the next challenge. Happy coding!!