Vue.js/Bulma datatable

Vue.js/Bulma sortable/filterable table.

by jeffory

HTML

<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/lodash.js"></script>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/bulma.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/font-awesome.css">
<div id="app">
<p class="control">
   <input v-model="search" placeholder="search" class="input">
</p>

<data-table inline-template :data="table_data" :columns="table_columns" :query="search">
    <div class="datatable">
        <table class="table">
            <thead>
                <tr>
                    <th v-for="column, index in view_columns">
                        <span>{{ column }}</span>

                        <div class="column-controls">
                            <span @click="column_toggle_sort(index)" class="icon is-small">
                                <i v-if="sort.index == index &amp;&amp; sort.desc" class="fa fa-sort-desc"></i>
                                <i v-else-if="sort.index == index &amp;&amp; !sort.desc" class="fa fa-sort-asc"></i>
                                <i v-else class="fa fa-sort"></i>
                            </span>
                        </div>
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="row in view_data">
                    <td v-for="column in row">
                        {{ column }}

                    </td>
                </tr>

                <tr v-if="view_data.length == 0">
                    <td :colspan="view_columns.length" class="has-text-centered">No data specified.</td>
                </tr>
            </tbody>
        </table>
    </div>
</data-table>
</div>

CSS

#app {
  margin: 10px;
}

.datatable {
  width: 100%
}

.column-controls {
  cursor: pointer;
  float: right;
  margin-top: 4px;
}

.column-controls .fa-sort {
  opacity: 0.4;
}

JavaScript

Vue.component('data-table', {
  props: [
    'data', 'columns', 'query', 'controls_column'
  ],
  data () {
    return {
      view_columns: [],
      view_data: [],
      data_map: {},
      slot_map: {},
      sort: {
        index: 0,
        desc: true
      }
    }
  },
  created() {
    this.parse_columns();
    this.parse_data();
    this.sort_data();
  },
  methods: {
    parse_columns() {
      this.columns.forEach((column, index) => {
        if (typeof(column) == 'string') {
          return this.view_columns.push(column);
        }

        this.view_columns.push(column[0]);

        if (column[1].startsWith('!')) {
          // This is a slot.

        } else if (column.length > 1) {
          // So this would make a map like { column_index: field }
          this.data_map[index] = column[1];
        }
      });
    },
    column_toggle_sort(column_index) {
      if (column_index == this.sort.index) {
        this.sort.desc = !this.sort.desc;
      } else {
        this.sort.index = column_index;
        this.sort.desc = true;
      }

      this.sort_data();
    },
    parse_data() {
      if (this.data == undefined) {
        return;
      }

      this.view_data = [];

      this.data.forEach((row_object) => {
        let row = [];

        // Assume an object if the data_map is set. Otherwise parse as a straight array.
        if (Object.keys(this.data_map).length > 0) {
          Object.keys(this.data_map).forEach((key) => {
            let value = this.data_map[key];

            if (value in row_object) {
              row.push(row_object[value]); 
            }
          });
        } else {
          row = row_object;
        }

        // Filter if a query is provided.
        if (typeof(this.query) !== "undefined" && this.query !== "") {
          let matched = false;

          row.forEach((cell) => {
            let query = String(this.query).toLowerCase().trim();

            if (String(cell).toLowerCase().indexOf(query) !== -1) {
     ...