JSFiddle - React, Tailwind, and code Playground

by David Waterston

HTML

<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<!-- component template -->
<script type="text/x-template" id="grid-template">
  <table>
    <thead>
      <tr>
        <th v-for="col in columns"
            v-if="col.visible"
          @click="sortBy(col.name)"
          :class="[{ active: sortKey == key }]"
          :style="{'text-align': col.align}">
          {{ col.label }}
          <span class="arrow" :class="sortOrders[col.name] > 0 ? 'asc' : 'dsc'">
          </span>
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="entry in data">
        <td v-if="col.visible" 
        	v-for="col in columns"
          :style="{'text-align': col.align}">
          {{ format(entry[col.name], col.formatter) }} 
        </td>
      </tr>
    </tbody>
  </table>
</script>

<!-- demo root element -->
<div id="demo">
  <form id="search">
    <input name="query" v-model="searchQuery">
  </form>
  <demo-grid
    :data="gridData"
    :columns="gridColumns"
    :filter-key="searchQuery">
  </demo-grid>
</div>

CSS

body {
  font-family: Helvetica Neue, Arial, sans-serif;
  font-size: 14px;
  color: #444;
}

table {
  background-color: #fff;
}

th {
  color: black;
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

td {
 
}

th, td {
  min-width: 120px;
  padding: 10px 20px;
}

th.active {

}

th.active .arrow {
  opacity: 1;
}

.arrow {
  display: inline-block;
  vertical-align: middle;
  width: 0;
  height: 0;
  margin-left: 5px;
  opacity: 0.66;
}

.arrow.asc {
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-bottom: 4px solid #fff;
}

.arrow.dsc {
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 4px solid #fff;
}

JavaScript

// register the grid component
Vue.component('demo-grid', {
  template: '#grid-template',
  props: {
    data: Array,
    columns: Array,
    filterKey: String
  },
  data: function () {
    var sortOrders = {}
    this.columns.forEach(function (key) {
      sortOrders[key] = 1
    })
    return {
      sortKey: '',
      sortOrders: sortOrders
    }
  },
  computed: {
    filteredData: function () {
      var sortKey = this.sortKey
      var filterKey = this.filterKey && this.filterKey.toLowerCase()
      var order = this.sortOrders[sortKey] || 1
      var data = this.data
      if (filterKey) {
        data = data.filter(function (row) {
          return Object.keys(row).some(function (key) {
            return String(row[key]).toLowerCase().indexOf(filterKey) > -1
          })
        })
      }
      if (sortKey) {
        data = data.slice().sort(function (a, b) {
          a = a[sortKey]
          b = b[sortKey]
          return (a === b ? 0 : a > b ? 1 : -1) * order
        })
      }
      return data
    }
  },
  filters: {
    capitalize: function (str) {
      return str.charAt(0).toUpperCase() + str.slice(1)
    }
  },
  methods: {
    sortBy: function (key) {
      this.sortKey = key
      this.sortOrders[key] = this.sortOrders[key] * -1
    },
    format: function (value, formatter) {
      if (formatter) {
        const formattedValue = formatter(value)
        if (formattedValue !== value) {
          return formattedValue
        }
      }
      return value
    }
  }
})


var demo = new Vue({
  el: '#demo',
  data: {
    searchQuery: '',
    gridColumns: [
    	{
      	name:'code',
        label: 'Code',
        type: 'string',
        sortable: true,
        visible: true,
        filterable: true,
        align: 'left',
        formatter: function (value) {
    			return value.toUpperCase();
    		}
        
       },
       {
      	name:'title',
        label: 'Title',
        type: 'string',
        sortable: true,
        visible: true,
 ...