If you have ever used online stores like Amazon or eBay, you have definitely used the preview feature. It shows you images or videos of the product so you know what to expect before making a purchase.

In this article, we are going to examine how to build a single page Amazon-like preview app with Vue.js

What we will build won’t look exactly like the Amazon website, but will exhibit the product preview characteristics.

Dependencies

To build this app, we are going to use a Node server for the back-end and Vue.js for our front-end. Before we get to work, you need to verify that you have a couple of things installed on your machine:

Building the Front-end

We going to use Vue.js to build the front-end. Vue.js is a progressive JavaScript framework that is quick and easy to use.

Installing Vue.js

You are going to need Vue.js installed on your machine. You can confirm your installation by running:

    vue --version

If you get a version number as a result then you have Vue.js installed. If not, it is recommended that you install the Vue CLI by running:

    npm install --global vue-cli

To create the frontend server, run the following :

    mkdir preview-app
    vue init webpack frontend

This creates a vue example project which we are now going to tweak and adjust.

Installing The Node Modules

We are going to use axios to make get requests from one of our Vue.js components so install it by running the following in the frontend directory:

    cd frontend
    npm install axios

Creating the Listing Component

Listing Component

This Listing component is responsible for showing all the products we have in the store and adding a link to the view for the product.

To create the Listing component, we run the following :

    touch Listing.vue

In the Listing.vue , we need to first import the axios module:

    <script>
    import axios from 'axios'
    //...

And now we use the module to fetch the product listing :

    //...
    export default {
      name: 'Listing',
      data () {
        return {
          products : []
        }
      },
      mounted : function(){
        axios.get('http://localhost:3128/products').
        then( result => {
          console.log( result );
          this.products = result.data;
        })
      }
    }
    </script>

We can see above that once the component is mounted, we make a call to our back-end server to fetch the available products and then assign it to the product data of the component.

The template for the component looks like this :

    <template>
      <div class="listing container">
        <div class="title" style="margin-bottom:40px;">
          <h1>Products on Sale</h1>
        </div>
        <div class="row">
          <div class="col-sm-2">
            <h2>#</h2>
          </div>
          <div class="col-sm-8">
            <h2>PRODUCT NAME</h2>
          </div>
          <div class="col-sm-2">
            <h2>GO TO</h2>
          </div>
        </div>

        <template v-for="product in products">
          <div class="row" style="margin-bottom:20px;">
            <div class="col-sm-2" >
              <p>{{ product.id }}</p>
            </div>
            <div class="col-sm-8">
              <p>{{ product.name }}</p>
            </div>
            <div class="col-sm-2">
              <router-link :to="{path: '/product/'+product.id }">View Product</router-link>
            </div>
          </div>
        </template>
      </div>
    </template>

In the template above, we list out the products as divs and add an action button that takes you to the single product page itself.

Creating the Preview Component

Product Preview Component

Product Preview Component

The Preview component is responsible for displaying data and images related to the selected product from the previous view. When the component is created, we make a get request to the backend server to fetch all the data for the particular id and then display the media in the form of a carousel on the right side of the screen.

Create the Preview.`vue` file by running :

    touch Preview.vue

In the Vue.js file, we first import the axios module :

    <script>
    import axios from 'axios'
    //...

Now, we build the component :

    //...
    export default {
      name: 'Preview',
      data () {
        return {
          media :[],
          product_name : "",
          product_desc : "",
          product_price : ""
        }
      },
      mounted : function(){
        // now we get all the related infomation for the particular product id
        axios.get(`http://localhost:3128/getProductInfo/${this.$route.params.id}`)
        .then( res => {
          this.media = res.data.media;
          this.product_name = res.data.product_name;
          this.product_desc = res.data.product_desc;
          this.product_price = res.data.product_price;

        })
      },
      methods : {
        initializePlayer : function(){
          console.log('here')
          var cld = cloudinary.Cloudinary.new({ cloud_name: "og-tech", secure: true});
          var demoplayer = cld.videoPlayer('video-player');
        }
      }
    </script>

After the post request is made, the model is updated with the data that was returned as a JSON response on the back-end.

Our view has a template that looks as follows :

    <template>
      <div class="preview">
        <div class="row">
          <div class="col-sm-6">
            <!--  this part will contain the product info -->
            <h1> {{ product_name }} </h1>
            <div>
              <p> {{ product_desc }} </p>
              <p> Price : ${{ product_price }} </p>
            </div>
          </div>
          <div class="col-sm-6">
            <!--  this part will contain the images -->
            <div id="demo" class="carousel slide" data-ride="carousel">
              <!-- Indicators -->
              <ul class="carousel-indicators">
                <template v-for="single_media in media">
                  <template v-if="single_media.id == 0">
                    <li data-target="#demo" v-bind:data-slide-to="single_media.id" class="active"></li>
                  </template>
                  <template v-else>
                    <li data-target="#demo" v-bind:data-slide-to="single_media.id"></li>
                  </template>
                </template>
                <!-- <li data-target="#demo" data-slide-to="0" class="active"></li>
                <li data-target="#demo" data-slide-to="2"></li> -->
              </ul>
              <!-- The slideshow -->
              <div class="carousel-inner">
                <template v-for="single_media in media">
                  <template v-if="single_media.id == 0">
                    <div class="carousel-item active">
                      <template v-if="single_media.type == 'image'">
                        <img class="img-responsive single-image" v-bind:src="single_media.url"/>
                      </template>
                      <template v-else>
                       <video
                        id="video-player"
                        controls
                        class="single-image cld-video-player cld-video-player-skin-dark"
                        v-bind:data-cld-source="single_media.url"
                        >
                        </video> 
                      </template>
                    </div>
                  </template>
                  <template v-else>
                    <div class="carousel-item">
                      <template v-if="single_media.type == 'image'">
                        <img class="img-responsive single-image" v-bind:src="single_media.url"/>
                      </template>
                      <template v-else>
                        <video
                        id="video-player"
                        controls
                        class="single-image cld-video-player cld-video-player-skin-dark"
                        v-bind:data-cld-source="single_media.url"
                        >
                        </video>
                      </template>
                    </div>
                  </template>
                </template>
              </div>
              <!-- Left and right controls -->
              <a class="carobbusel-control-prev" href="#demo" data-slide="prev">
                <span class="carousel-control-prev-icon"></span>
              </a>
              <a class="carousel-control-next" href="#demo" data-slide="next"  v-on:click="initializePlayer()">
                <span class="carousel-control-next-icon"></span>
              </a>
            </div>  
          </div>
        </div>

      </div>
    </template>

In the template above, what we want to achieve is to display the media for the particular product. If you take a look at when we built the component, we make a request to the backend and then send the response to the Vue component.

We need to know if the media being displayed is an image or a video. So we check in the template;

    //..
    <template v-if="single_media.type == 'image'">
      <img class="img-responsive single-image" v-bind:src="single_media.url"/>
    </template>
    <template v-else>
     <video
      id="video-player"
      controls
      class="single-image cld-video-player cld-video-player-skin-dark"
      v-bind:data-cld-source="single_media.url"
      >
      </video> 
    </template>
    //..

If it has type of image, we display the image in the carousel but if type is a video, we use the Cloudinary VIdeo Player to display the video. To initialize the video player, we add v-on:click event to the > button. Once the button is clicked, the video player is initialized with the video.

PS: Cloudinary’s video player also playing videos by tags and playing playlists. You can read more about it here.

The preview view has some scoped styling as follows :

    <style scoped>
      h1, h2 {
        font-weight: normal;
      }
      ul {
        list-style-type: none;
        padding: 0;
      }
      li {
        display: inline-block;
        margin: 0 10px;
      }
      a {
        color: #42b983;
      }
      .carousel-inner{
        height : 500px;
      }
      .carousel-item{
        height : 100%;
      }
      .single-image{
        width : 100%;
        height: 100%;
        object-fit : fill;
      }
      #demo{
        margin-left: 30px;
        margin-right: 30px;
      }
    </style>

Linking Components

To allow for flow from one component to another, Vue has what is called the vue-router. Open the frontend/src/router/index.js file and edit it to look like this:

    // frontent/src/router/index.js

    import Vue from 'vue'
    import Router from 'vue-router'
    import HelloWorld from '@/components/HelloWorld'
    import Listing from '@/components/Listing'
    import Preview from '@/components/Preview'

    Vue.use(Router)

    export default new Router({
      routes: [
        {
          path: '/',
          name: 'Listing',
          component: Listing
        },
        {
          path: '/product/:id',
          name: 'Product',
          component: Preview
        }
      ]
    })

This specifies the available routes for the application. Earlier on in the components, we used <router-link> to move from the Listing component to the Preview component and this is the file that handles what you get. You can add a lot more options when creating routers. To read more about it, head over here.

Building the Back-end

To build our back-end, we need to change directory to the root directory of our application and then install the node modules:

    cd preview-app 
    npm install cors express body-parser dotenv request connect-multiparty cloudinary

Once this is done, you have successfully installed all the modules necessary for you to build the project.

Create a server.js file

Now we need to create a file that will contain the instructions for our server to work In your video-suggestion directory,

    touch server.js

This will be the start-up file that will be referenced when your server is running In your server.js file, you need to

Import the node modules

    require('dotenv').config()
    const cors       = require('cors')
    const express    = require('express')
    const bodyParser = require('body-parser')
    const multipart  = require('connect-multiparty')
    const request    = require('request')
    const cloudinary = require('cloudinary')

    //...

Once you have imported your node modules, you can then use them freely all through your script.

Create your express app

Now we create our express app instance by adding the following to the server.js

    //...

    const app = express()

    //...

Load the middlewares

We load the middlewares in our server.js by adding the following

    //...

    app.use(cors())
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    const multipartMiddleware = multipart();

    //...

Here, we set our app to use cors . We also instructed the app the parse the requests in JSON format.

Configure the Cloudinary Client

We need to configure our cloudinary client using your CLOUD_NAME, API_KEY and API_SECRET

    //...

    cloudinary.config({
        cloud_name: 'CLOUDINARY_CLOUD_NAME', 
        api_key: 'CLOUDINARY_API_KEY', 
        api_secret: 'CLOUDINARY_API_SECRET'
    });

    //...

Once this is done, then we have successfully configured our Cloudinary client.

Create app routes

Our back-end server is very simple, it’s an express web server with two major routes:

  • /products - Lists all the products available for sale.
  • /getProductInfo - Returns data for the selected product.
    //...
    app.get('/products', multipartMiddleware, function(req, res){
      return res.json([
        {id: '1', name: 'UltraLight Mechanical Keyboard'},
        {id: '121', name: 'IPhone X'},
        {id: '23', name: 'Tesla S'},
        {id: '42', name: 'Work Shoes'}
      ]);
    });

    app.get('/getProductInfo/:id', multipartMiddleware, function(req, res){
      console.log( req.params.id );
      return res.json({
        media:        [
          {
            id:       '0',
            type:     'image',
            url:      'https://static.pexels.com/photos/265631/pexels-photo-265631.jpeg'
          },
          [...]
          {
            id:       '3',
            type:     'video',
            url:      
                'http://res.cloudinary.com/og-tech/video/upload/s--ZWPqo282--/v1514966645/sea_turtle-short_z1pr4o.mp4'
          },
        ],
        product_name: 'Ultra Thin Mechanical Keyboard',
        product_desc: 'This keyboard gives you the clack to your click',
        product_price: '200'
      })
    });

    //...

In the above, we see the routes returning responses in JSON format for it to be further used at the frontend. You may have observed that a lot (all) the data returned to the user was static. In a real-world application, you would return dynamic data to the user.

The /productInfo route accepts the id of your product so that is what you would use to identify what data to serve instead of just returning static json data. In other words, you can make further query to your database or cloud storage to fetch the information and return the data in the format used above.

Configure Application Port

Now we set the port we want the app to listen on:

    [...]

    let port = 3128 || process.env.PORT;

    app.listen(port, function () {
      console.log('App listening on port ' + port + '!');
    });

Application Demo

Conclusion

In this article we have see how to leverage Cloudinary’s image and video capabilities using Vue.js to build an Amazon-like preview app for products.

You also can head over here if you want to find out more about optimizing images served on your website f and more about improving your store’s SEO by engineering better Google mobile search ranking through image optimization.

Now you can use concepts shared here in building your own application for the #NextBillionUsers

Here a link to the GitHub repository for more references.