4
\$\begingroup\$

Because this is my very first attempt to create a data grid with Vue, I would like to know about all misconceptions used in my approach. Whomever points to the most important shortcoming / weakness in my approach will receive 50 points :).

var url ='https://api.mockaroo.com/api/b5f38710?count=20&key=cdbbbcd0'
new Vue({
  el: '#datagrid',
  data: {
    columns: {
      id: 'ID',
      nick: 'Nick name',
      first: 'First name',
      last: 'Last name'
    },
    users: [],
    query: '',
    prevKey: 'id',
    orderDesc: false
  },
  methods: {
    async loadUsers () {
      var result = await axios.get(url)
      this.users = result.data
      this.query = ''
      this.prevKey = 'id'
      this.orderDesc = false
    },
    clearQuery () {
      this.query = ''
    },
    sortUsers (e) {
      var key = e.target.dataset.key
      if (this.prevKey === key) {
        this.users.reverse()
        this.orderDesc = !this.orderDesc
      } else {
      	this.users = _.sortBy(this.users, key)
        this.orderDesc = false
      	this.prevKey = key
      }
    },
    filterUsers (users) {
      return _.filter(users, user =>
        _.find(user, prop =>
          new RegExp(this.query, 'i').test(prop)
        ) 
      )
    },
    updateQuery: _.debounce(function (e) {
      this.query = e.target.value
    }, 350)
  }
})
#datagrid {
  width: 98%;
  margin: 10px auto;
  padding: 2px;
  background-color: white;
  border: 2px solid #3F51B5;
  overflow: hidden;
}
.table {
  display: table;
  width: 100%;
  font-size: 14px;
  font-family: Arial, sans-serif;
  color: #263238;
}
.thead {
  display: table-header-group;
}
.tbody {
  display: table-row-group;
}
.tr {
  display: table-row;
}
.td {
  display: table-cell;
  position: relative;
}
.thead .td {
  padding: 5px 14px;
  background-color: #3F51B5;
  color: white;
  font-weight: bold;
  text-align: center;
  cursor: pointer;
}
.tbody .td {
  padding: 4px;
  color: #263238;
  text-align: center;
}
.tr .td:not(:last-child) {
  border-right: 2px solid white;
}
.tbody .tr:hover .td {
  background-color: #C5CAE9;
}
.tbody .tr:hover .td:not(:last-child) {
  border-right: 2px solid #C5CAE9;
}
.tools {
  margin: 10px;
  box-sizing: border-box;
}
.tools:after {
  content: "";
  display: block;
  clear: both;
}
.source {
  display: block;
  float: left;
}
.search {
  float: right;
}
.arrow {
  display: inline-block;
  position: absolute;
  width: 0;
  height: 0;
  margin-left: 5px;
  margin-top: 5px;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  transition: all .6s;
}
.asc {
  border-bottom: 6px solid white;
}
.desc {
  border-top: 6px solid white;
}
.users-move {
  transition: transform .6s;
}
.users-enter-active, .users-leave-active {
  transition: all .6s;
}
.users-enter, .users-leave-to {
  opacity: 0;
  transform: translateY(30px);
}
<script src="https://unpkg.com/underscore@1.8.3/underscore-min.js"></script>
<script src="https://unpkg.com/vue@2.4.3/dist/vue.min.js"></script>
<script src="https://unpkg.com/axios@0.16.2/dist/axios.min.js"></script>

<div id="datagrid">
  <div class="tools">
    <button class="source" @click="loadUsers">
      Load some fake data from mockaroo.com
    </button>   
    <div class="search">
      <input type="text" @input="updateQuery" :value="query">
      <button class="clear" @click="clearQuery">clear</button>
    </div>
  </div>
  <div class="table">
    <div class="thead" @click="sortUsers">
      <div class="tr">
        <span v-for="(col, key) in columns" class="td" :data-key=key>
          {{ col }}
          <span
            v-if="prevKey === key"
            :class="['arrow', orderDesc ? 'desc' : 'asc']">
          </span>
        </span>
      </div>
    </div>
    <transition-group name="users" tag="div" class="tbody">
      <div class="tr" v-for="row in filterUsers(users)" :key="row.id">
        <span class="td" v-for="column in row">{{ column }}</span>
      </div>
    </transition-group>
  </div>
</div>

If the snippet doesn't load the data: Mockaroo has an SSL certificate problem. So you must open the data URL in your browser first, confirm the security exception, delete the downloaded JSON and then run this code snippet again.

\$\endgroup\$
  • \$\begingroup\$ Your "Load some fake data from mockaroo.com" doesn't appear to do anything. Did you check whether it still works as snippet? \$\endgroup\$ – Mast Sep 17 '17 at 16:44
  • 1
    \$\begingroup\$ @Mast Mockaroo have ssl certificatie problem. So you must open data url in browser first ( api.mockaroo.com/api/b5f38710?count=20&key=cdbbbcd0 ), confirm security exception, delete downloaded json a then run my code snippet again. \$\endgroup\$ – user145018 Sep 18 '17 at 13:36
  • \$\begingroup\$ Ah, that makes sense. Please add that information to the question itself. Comments are kind-of third-rate citizens on the Stack Exchange network and may be removed in the future. Not having the note may get your question closed in the future ;-) I'm no JS expert, but I hope you get some great answers. \$\endgroup\$ – Mast Sep 18 '17 at 13:58
1
\$\begingroup\$

Feedback

Your code is really solid. It makes great use of async functions, arrow functions, transitions, etc.

Suggestions

The only things I could think of that could be changed are:

  1. Put the filtered users in a computed property. One could argue that the difference is small - it adds a couple lines, and the function is technically still called the same amount of times, but it takes the function call out of the markup.

    computed: {
        filteredUsers: function() {
          return _.filter(this.users, user =>
            _.find(user, prop =>
              new RegExp(this.query, 'i').test(prop)
            ) 
          ) ;
        }
      },    
    

    Then use that when iterating over the users:

    <div class="tr" v-for="row in filteredUsers" :key="row.id">
    
  2. Since the code already contains arrow functions, continue with es6 features and use let and const instead of var unless browser support is an issue. For reasons why, see this answer on SE and its siblings.

    For example, the first line of the loadUsers method could be changed from

     var result = await axios.get(url)
    

    To the following, which can't be re-assigned:

    const result = await axios.get(url)
    
  3. I would also consider using v-model for the query input - i.e.

     <input type="text" @input="updateQuery" :value="query">
    

    updated to:

    <input type="text" v-model="query">
    

    But I am not sure about replicating the debounce effect in updateQuery()... maybe that could be achieved with a watcher (perhaps making it more complicated).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy