Praktické príklady s Vue – 1/x

Keď už sme v predchádzajúcom článku s tabuľkami začali, lepšie povedané v druhej časti skončili príkladom so znovu použiteľným komponentom, ktorý vykresloval dve nezávislé tabuľky s rôznymi údajmi, tak prvý z praktických príkladov bude nečakane …

Tabuľka

Ale tento krát je “fulltextovo” filtrovateľná a zoraditeľná podľa stĺpca – stačí kliknúť na názov stĺpca. A ako bonus prebehne zoradenie animovane…

Celý príklad je zámerne napísaný takto:

  • Definícia šablóny tento krát nie je súčasťou JavaScriptu, ale vyňatá do TEMPLATE značky. Po prvé preto, aby ste videli, že aj tak to ide, a po druhé preto, aby viniklo, že JavaScript pre túto tabuľku vôbec nie je dlhý, ani komplikovaný.
  • Animácie sú použité dvoma spôsobmi. Aj pomocou príslušných CSS tried, aj pomocou externej Velocity knižnice.
  • Na zoraďovanie údajov a filtrovanie riadkov na základe vyhľadávanie je zasa zámerne využitá Underscore knižnica. Netreba nanovo vymýšľať koleso a skúsiť to písať po svojom. Za tých 5 či 6 kilobajtov navyše nestojí za to lámať si hlavu vlastným algoritmom.
  • No a tabuľka je tabuľkou urobená pomocou CSS, značka TABLE nebola použitá. To kvôli tomu, aby šli presúvať celé riadky tabuľky – TR značky. Avšak CSS berte “as is”. Žiadny SASS, LESS, ani len BEM, o CSS tu naprosto nejde, tak som ho flákol.

Príklad je ako vždy plne funkčný, ale stále platí, že ak by predsa len nefungoval a nezobrazoval zoznam užívateľov, je to problém s certifikátom mockaroo.com. Kliknite na nasledovné url zdroja údajov ( https://api.mockaroo.com/api/b5f38710?count=8&key=cdbbbcd0 ) – otvorí sa v novom okne – a potvrďte bezpečnostnú výnimku. Že to funguje sa prejaví tak, že vám z tej url stiahne json súbor. Ten len zmažte, zatvorte kartu a znovu spustite fiddle prepnutím sa z karty Result na napríklad na kartu HTML a potom späť na Result.

<style>
.data-grid {
  width: 98%;
  margin: 10px auto;
  padding: 2px;
  background-color: white;
  border: 2px solid #3F51B5;
  overflow: hidden;
}
.table {
  display: table;
  width: 100%;
  font-size: 13px;
  font-family: Arial, sans-serif;
  color: #263238;
  cursor: pointer;
}
.thead {
  display: table-header-group;
}
.tbody {
  display: table-row-group;
}
.tr {
  display: table-row;
}
.td {
  display: table-cell;
  position: relative;
}
.tr .td:not(:last-child) {
  border-right: 2px solid white;
}
.thead .td {
  padding: 5px 14px;
  background-color: #3F51B5;
  color: white;
  font-weight: bold;
  text-align: center;
}
.tbody .td {
  padding: 4px;
  color: #263238;
  text-align: center;
}
.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;
}
.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);
}
</style>

<div id="app">
  <data-grid
    url="https://api.mockaroo.com/api/b5f38710?count=8&key=cdbbbcd0"
    :columns="{id: 'ID', nick: 'Nick name', first: 'First name', last: 'Last name'}"
  ></data-grid>
  <data-grid
    url="https://api.mockaroo.com/api/03f2c610?count=8&key=cdbbbcd0"
    :columns="{id: 'ID', first: 'First name', last: 'Last name', email: 'Email'}"
  ></data-grid>
</div>

<template id="data-grid">
  <transition
    appear
    v-on:before-appear="onCreate"
    v-on:appear="onData"
  >
    <div class="data-grid">
      <div class="tools"> 
        <div class="search">
          <input
            type="text"
            @input="updateQuery"
            :value="query"
            placehorder="search..."
          >
          <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 filteredUsers" :key="row.id">
            <span class="td" v-for="column in row">{{ column }}</span>
          </div>
        </transition-group>
      </div>
    </div>
  </transition>
</template>

<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.5.0/velocity.min.js"></script>
<script src="https://unpkg.com/underscore@1.8.3/underscore-min.js"></script>
<script src="https://unpkg.com/vue@2.4.4/dist/vue.min.js"></script>
<script src="https://unpkg.com/axios@0.16.2/dist/axios.min.js"></script>

<script>
Vue.component('data-grid', {
  template: '#data-grid',
  props: ['url', 'columns'],
  data () {
    return {
      users: [],
      query: '',
      prevKey: 'id',
      orderDesc: false
    }
  },
  computed: {
    filteredUsers () {
      return _.filter(this.users, user =>
        _.find(user, prop =>
          new RegExp(this.query, 'i').test(prop)
        )
      )
    }
  },
  methods: {
    sortUsers (evt) {
      var key = evt.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
      }
    },
    updateQuery: _.debounce(function (evt) {
      this.query = evt.target.value
    }, 300),
    clearQuery () {
      this.query = ''
    },
    onCreate (elm) {
      elm.style.opacity = 0
    },
    async onData (elm) {
      this.users = await axios
        .get(this.url)
        .then(res => res.data)
      Velocity(elm, "fadeIn", {duration: 600})
    } 
  }
})

new Vue({
  el: '#app'
})
</script>

[iframe src=”https://jsfiddle.net/provuecateur/xbre1er8/embedded/result,html,css,js/” width=”100%” height=”600″]