Vue.js 2.0 dynamic table (inline)

Vue.js 2.0 dynamic table (inline)

by Michael Menaker

HTML

<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<!-- component template -->
<script type="text/x-template" id="grid-template">
  <table>
    <caption>
      <slot name="caption">&nbsp;</slot>
    </caption>
    <thead>
      <tr>
        <th v-for="key in columns" @click="sortBy(key)" :class="{ active: sortKey == key }">
          {{ key | getDisplayName(displayNames) }}
          <span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
          </span>
        </th>
      </tr>
    </thead>
    <tbody v-for="(entry, index) in filteredData">
      <tr @dblclick="childShow.splice(index, 1, true)">
        <td v-for="key in columns">
          <slot :name="key" :entry="entry">
            {{entry[key]}}
          </slot>
        </td>
      </tr>
      <tr v-if="$scopedSlots.child && childShow[index]" @dblclick="toggleChild(index)">
        <td :colspan="columns.length">
          <slot name="child" :entry="entry"></slot>
        </td>
      </tr>
    </tbody>
  </table>
</script>

<!-- demo root element -->
<div id="demo">
  <form id="search">
    Search
    <input name="query" v-model="searchQuery">
  </form>
  <demo-grid :aggregate-columns="true" :data="gridData" :filter-key="searchQuery" :display-names="displayNames" :child-hideable="true" :child-init-hide="true">
    <!-- <demo-grid :data="gridData">-->
    <template slot="caption">This is my caption</template>
    <!-- <template slot="child" scope="props">
    This is my child row: {{props.entry.name}}
    </template>-->
    <template slot="child" scope="props">
      This is my child row: {{props.entry.name}}
    </template>
    <template slot="name" scope="props">
      <b>{{props.entry.name}}</b>
      <br />
      <button @click="showPower(props.entry.power)">
        Show Power
      </button>
    </template>
  </demo-grid>
</div>

CSS

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

table {
  border: 2px solid #42b983;
  border-radius: 3px;
  background-color: #fff;
}

th {
  background-color: #42b983;
  color: rgba(255, 255, 255, 0.66);
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

td {
  background-color: #f9f9f9;
}

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

th.active {
  color: #fff;
}

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,
    columnsToDisplay: {
      type: Array,
      default: function() {
        return [];
      }
    },
    aggregateColumns: {
      type: Boolean,
      default: false
    },
    displayNames: {
      type: Object,
      default: function() {
        return {};
      }
    },
    filterKey: {
      type: String,
      default: ""
    },
    childHideable: {
      type: Boolean,
      default: false
    },
    childInitHide: {
      type: Boolean,
      default: false
    }
  },
  data() {
    var sortOrders = {};
    this.getColumns(this.columnsToDisplay, this.data).forEach(key => sortOrders[key] = 1);

    var childShow = [];
    if (this.childInitHide) {
      this.data.forEach(entry => childShow.push(false));
    } else {
      this.data.forEach(entry => childShow.push(true));
    }

    return {
      sortKey: '',
      sortOrders: sortOrders,
      childShow: childShow
    }
  },
  computed: {
    filteredData() {
        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(row => {
            return Object.keys(row).some(key => {
              return String(row[key]).toLowerCase().indexOf(filterKey) > -1
            })
          })
        }
        if (sortKey) {
          data = data.slice().sort((a, b) => {
            a = a[sortKey]
            b = b[sortKey]
            return (a === b ? 0 : a > b ? 1 : -1) * order
          })
        }
        return data
      },
      columns() {
        return this.getColumns(this.columnsToDisplay, this.data);
      }
  },
  filters: {
    getDisplayName(column, displayNames) {
      if (column in displayNames) {
        return displayNames[column];
      } else {
        return column.charAt(0).toUpperCase() +...