<!DOCTYPE html>
<html>

  <head>
    <link rel="stylesheet" href="style.css" />
  </head>

  <body>
    <h1 class="text-center">Responsive Tiles Vue Component Example</h1>
    <div id="app"><span class="loader">Loading...</span></div>
    <div id="footer">Images courtesy of <a href="https://flickr.com/photos/janumedia">I Nengah Januartha</a></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.5/require.min.js"></script>
    <script src="script.js"></script>
  </body>

</html>
(function()
{
  require.config(
  {
    paths:{
      Vue: "https://vuejs.org/js/vue",
      vue: "https://rawgit.com/edgardleal/require-vuejs/master/dist/require-vuejs"
      /*vue: "https://cdn.rawgit.com/edgardleal/require-vuejs/aeaff6db/dist/require-vuejs.min"*/
    }
  });
  
  require(["vue!Tiles", "data.json?callback=define"], 
          function(Tiles, data)
  {
    new Tiles(document.getElementById("app"), data.list);
  });
  
})();
# Responsive Tiles Grid Vue Component
A Tiles Grid VueJS component and relying to RequireJS as module and data (json) loader

# Features
   - Responsive
   - 2 Columns Tiles
   - Centering last row when only has single item
   - JSON data

# Libraries
  - VueJS
  - RequireJS

# Where to use?
Any website project that use client browser rendering
<template>
  <div id="container">
    <ul id="tiles-container" class="table">
      <li v-for="(item, index) in list" :class="getClass(index)">
        <div>
          <img :src="item.img">
        </div>
        <div class="info">
          <h2>{{ item.name }}</h2>
          <div v-html="item.desc"></div>
          <br>
          <div class="button-group">
            <button>More Info</button>
          </div>
        </div>
      </li>
    </ul>
  </div>
</template>
<script>
  define(["Vue"], function(Vue)
  {
    
    return function(el, list)
    {
      Vue.component("tiles",
      {
        template: template,
        props:
        {
          list: {
            type:Array,
            required: true
          }
        },
        mounted: function()
        {
          //listen to resize event, make sure tile size follow the window size
          if(window.addEventListener)
          {
              window.addEventListener("resize", this.adjustHeight);    
          } else 
          {
              window.attachEvent("onresize", this.adjustHeight);    
          }
          //also important to listen image onload as will resize the tile
          var self = this;
          var images = document.querySelectorAll("#tiles-container li img");
          [].slice.call(images).forEach(function(img)
          {
              img.onload = function(e)
              {
                self.adjustHeight();
              }
          });
          //initial set
          this.adjustHeight();
        },
        methods:
        {
          getClass: function(index)
          {
            var len = this.list.length;
            if(len%2 == 0 && index == len-2)
            {
              return "last-2nd";
            } else if (len%2 == 1 && index == len-1)
            {
              return "last-center";
            }
            return "";
          },
          adjustHeight: function()
          {
            var tiles = document.querySelectorAll("#tiles-container > li"),
            buttonStyle, maxH = 0;
            
            //reset to allow autosize
            [].slice.call(tiles).forEach(function(tile)
            {
              tile.style.height = "";
              buttonStyle = tile.querySelector(".button-group").style;
              buttonStyle.position = "relative";
            });
            //get max Height
            [].slice.call(tiles).forEach(function(tile)
            {
              maxH = Math.max(maxH, tile.offsetHeight);
            });
            //implement max Height to all tiles
            [].slice.call(tiles).forEach(function(tile)
            {
              tile.style.height = maxH + "px";
              buttonStyle = tile.querySelector(".button-group").style;
              buttonStyle.position = "absolute";
              buttonStyle.bottom = "12px";
              buttonStyle.left = 0;
              buttonStyle.right = 0;
            });
          }
        }
      });
      
      //render component
      el.innerHTML = "<tiles :list='listArr'></tiles>";
      
      var app = new Vue(
      {
        el: el,
        data: 
        {
          listArr: list
        }
      });
    }
  
  });
</script>
define({
  list: [{
    name: "Sanur",
    desc: "Sunrise at Bali's ealiest beach resorts",
    img: "https://c2.staticflickr.com/6/5327/8975420033_9eeb1aed1f.jpg"
  }, {
    name: "Tanah Lot",
    desc: "One of most popular tourist destination in the world",
    img: "https://c1.staticflickr.com/5/4093/4785053162_0c990c21d1.jpg"
  }/*, {
    name: "Yeh Embang",
    desc: "Mount Batukaru from Yeh Embang, Jembrana Bali",
    img: "https://c2.staticflickr.com/2/1231/4726659769_9e56963d80.jpg"
  }*/, {
    name: "Karang Bolong",
    desc: "Majesty of Karang Bolong at Yeh Gangga Beach",
    img: "https://c1.staticflickr.com/5/4137/5221388030_8ef60c71b9.jpg"
  }]
})
/** Variables **/

$body-bg-color: lightgrey;
$tile-bg-color: white;
$button-color: grey;

.transition {
  -webkit-transition-duration: 0.4s;
  -moz-transition-duration: 0.4s;
  -o-transition-duration: 0.4s;
  transition-duration: 0.4s;
}
@mixin width-calc {
  width: -webkit-calc(50% - 20px);
  width: -moz-calc(50% - 20px);
  width: calc(50% - 20px);
}

body {
  font-family: sans-serif;
  font-size: 16px;
  margin: 0 auto;
  padding: 0;
  background-color: $body-bg-color;
}

h1 {
  font-size: 1.2em;
}

.text-center {
  text-align:center;
}

.table {
  &:before,
  &:after {
    content: "";
    display: table;
  }
  &:after {
    clear: both;
  }
}

.loader {
  @extend .text-center;
}

/** TILES **/
#tiles-container {
  max-width: 900px;
  margin: 0 auto;
  padding: 0 20px;
  
  li {
    @include width-calc;
    background-color: $tile-bg-color;
    margin-top: 0px;
    margin-left: 20px;
    margin-right: 0px;
    margin-bottom: 40px;
    float: left;
    position: relative;
    list-style: none;
    
    &:nth-child(odd) {
      margin-left: 0;
      margin-right: 20px;
    }
    
    @media only screen and (max-width:560px) {
      width: 100%;
      margin-top: 0px;
      margin-left: 0px;
      margin-right: 0px;
      margin-bottom: 40px;
      
      &:nth-child(odd){
        margin-left: 0;
        margin-right: 0;
      }
      
      &:first-child,
      &:nth-last-child(2) {
        margin-bottom: 40px;
      }
    }
    
    @media only screen and (max-width:350px) {
      margin-bottom: 20px;
    }
  }
  
  li:last-child,
  li.last-2nd {
      margin-bottom: 0px;
      @media only screen and (max-width:560px) {
        margin-bottom: 40px;
      }
  }
  li.last-center {
    margin-left: 25%;
    margin-right: 25%;
    @media only screen and (max-width:560px) {
      margin-left: 0;
      margin-right: 0;
      margin-bottom: 40px;
    }
  }
  
  img {
    width: 100%;
  }
  
  .info {
    padding: 20px;
    text-align: center;
  }
  
  h2 {
    font-size: 1.2em;
  }
  
  button {
    border: 1px solid $button-color;
    border-radius:0;
    background-color: inherit;
    padding: 8px 16px;
    margin-bottom: 14px;
    color: $button-color;
    font-size: 0.6em;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    cursor: pointer;
    @extend .transition;
    &:hover {
      background-color:$button-color;
      color:white;
    }
  }
}

#footer {
  margin: 20px auto;
  text-align: center;
  font-size: 0.6em;
  color: grey;
  a {
    text-decoration: none;
    outline: none;
    color: inherit;
    &:hover {
      text-decoration: underline;
    }
  }
}