JSFiddle - React, Tailwind, and code Playground

by Manna

HTML

<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.3/lodash.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<div id="app" class="container">
  <h1>
    Active Users
    <button v-if="users.length" @click="fetchUsers" class="btn btn-primary">
      <span class="glyphicon glyphicon-refresh"></span>
      Refresh with latest activity
    </button>
  </h1>
  <input v-model="filterQuery" placeholder="Filter users" class="form-control">
  <table v-if="filteredUsers.length" class="table">
    <thead>
      <tr>
        <th v-for="column in tableColumns">
          <a @click="orderByField = column.field">
            {{ column.title }}
          </a>
        </th>
      </tr>
    </thead>
    <tbody is="transition-group" name="user-list">
      <tr v-for="user in filteredUsers" :key="user.id">
        <td v-for="column in tableColumns">
          {{ getField(user, column.field) }}
        </td>
      </tr>
    </tbody>
  </table>
  <p v-if="statusMessage" class="well">
    {{ statusMessage }}
  </p>
</div>

CSS

th a { cursor: pointer; }
h1 button { float: right; }
.user-list-enter, .user-list-leave-active {
  display: none;
}
.user-list-move {
  transition: transform .5s;
}

JavaScript

new Vue({
  el: '#app',
  data: {
    tableColumns: [
      {
        title: 'Name',
        field: 'name'
      }, {
        title: 'Company',
        field: 'company.name'
      }, {
        title: 'Website',
        field: 'website'
      }
    ],
    users: [],
    filterQuery: '',
    orderByField: 'name',
    fetchError: false
  },
  computed: {
    filteredUsers: function () {
      var vm = this
      return _.orderBy(
        vm.users.filter(function (user) {
          var regex = new RegExp(vm.filterQuery, 'i')
          return (
            regex.test(user.name) ||
            regex.test(user.company.name) ||
            regex.test(user.website)
          )
        }), 
        vm.orderByField
      )
    },
    statusMessage: function () {
    	if (this.fetchError) {
      	return 'There was a problem fetching the users. JSONPlaceholder might be down.'
      }
    	if (this.users.length) {
      	if (!this.filteredUsers.length) {
         	return 'Sorry, no matching users were found.'
        }
      } else {
      	return 'Loading...'
      }
    }
  },
  created: function () {
    this.fetchUsers()
  },
  methods: {
    fetchUsers: function () {
      var vm = this
      vm.users = []
      vm.fetchError = false
      fetch('https://jsonplaceholder.typicode.com/users')
        .then(function (response) { return response.json() })
        .then(function (users) { vm.users = users })
        .catch(function () { vm.fetchError = true })
    },
    getField: function (object, field) {
    	return _.at(object, field)[0]
    } 
  }
})