Praktické príklady s Vue – 2/x

V predošlom príklade sme si tabuľku plnili AJAX-om falošnými údajmi. V tomto teda budeme modernejší a budeme si vykreslovať reálne údaje a dokonca v reálnom čase, pomocou WebSockets technológie. Konkrétne to budú kurzy krypto mien Bitcoin, Litecoin a Ethereum, prevzaté z Bitstamp.net.

A tento krát responzívne,

vrátane ukážky ďalších vlastností Vue, ako “conditional rendering”, “content distribution with slots”, “filters”, či “class bindings” – pripájania CSS tried na základe podmienok, či výpočtov. Ešte pár vysvetliviek ku kódu:

  • Ako zdroj údajov nám slúži Bitstamp public API, ktoré však nepoužíva websockets priamo, ale pomocou knižnice Pusher. Pusher je wrapper nad websockets implementujúci pub/sub pattern za účelom abstrakcie – ešte jednoduchšieho použitia websockets.
  • Knižnica Numerals slúži na formátovanie čísiel. V našom prípade ceny na dve desatinné miesta, podľa slovenských pravidiel (napr. desatinná čiarka, miesto bodky).
  • Komponenty sú tento krát použité dva, nie jeden. Z toho jeden je registrovaný lokálne a druhý globálne a jeden má klasickú šablónu (template), druhý zasa tzv. X-template. Len aby ste videli, že to ide viacerými spôsobmi a aby ste si všimli, že kým globálny komponent nemusím nikde spomenúť, lokálny som musel inštancii Vue vyslovene dať na vedomie.
  • No a CSS som tento krát neflákol, ale ani sa s ním nehral. Aby bol príklad responzívny, použil som Bulma framework. Ten som zvolil predovšetkým preto, že je to “pure CSS” framework, čiže bez JavaScriptu a preto, že je flex-based a tak nám idú vytvoriť rovnako vysoké panely bez ohľadu na veľkosť obsahu obsahu – niečo, čo v Bootstrap(3) nejde.
<link href="https://unpkg.com/bulma@0.5.3/css/bulma.css" rel="stylesheet">

<div id="app">
  <app-layout>
    <price-panel pair="btc/eur" :conn="pusher"></price-panel>
    <price-panel pair="ltc/eur" :conn="pusher"></price-panel>
    <price-panel pair="eth/eur" :conn="pusher"></price-panel>
  </app-layout>
</div>

<script id="app-layout" type="text/x-template">
  <div class="tile is-ancestor">
    <div class="tile is-parent">
      <slot></slot>
    </div>
  </div>
</script>

<template id="price-panel">
  <article class="tile is-child box has-text-centered">
    <p class="title">AKTUÁLNA CENA</p>
    <p class="subtitle">Pre menový pár {{ pair | capitalize }}</p>
    <p v-if="!price">
      Čakajte prosím, kým sa neuskutoční nový obchod s&nbsp;touto menou.
    </p>
    <p v-else class="content is-size-4 has-text-weight-bold" :class="[color]">{{ price | skFormat }}</p>
  </article>
</template>

<script src="https://unpkg.com/vue@2.4.4/dist/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/4.1.0/pusher.min.js"></script>
<script src="https://unpkg.com/numeral@2.0.6/min/numeral.min.js"></script>
<script src="https://unpkg.com/numeral@2.0.6/min/locales/sk.min.js"></script>

<script>
  var pricePanel = {
    template: '#price-panel',
    props: ['pair', 'conn'],
    data () {
      return {
        subscription: null,
        price: ''
      }
    },
    computed: {
      channel () {
        return 'live_trades_' + this.pair.replace('/', '')
      },
      color () {
        switch (this.pair) {
          case 'btc/eur': return 'has-text-warning'
          case 'ltc/eur': return 'has-text-grey-light'
          case 'eth/eur': return 'has-text-primary'
          default: return ''
        }
      }
    },
    filters: {
      skFormat (price) {
        if (!price) return ''
        return numeral(price).format('0.00') + ' €'
      },
      capitalize (pair) {
        return pair.toUpperCase()
      }
    },
    methods: {
      onTrade (trade) {
        this.price = trade.price
      },
      subscribe () {
        this.subscription = this.conn.subscribe(this.channel)
        this.subscription.bind('trade', this.onTrade)
      },
      unsubscribe () {
        this.subscription.unbind('trade', this.onTrade)
        this.conn.unsubscribe(this.channel)
      }
    },
    created() {
      this.subscribe()
    },
    beforeDestroy(){
      this.unsubscribe()
    }
  }

  Vue.component('app-layout', {
    template: '#app-layout'
  })

  new Vue({
    el: '#app',
    components: {
      'price-panel': pricePanel
    },
    data: {
      pusher: null
    },
    created () {
      this.pusher = new Pusher('de504dc5763aeef9ff52')
      numeral.locale('sk')
    }
  })
</script>

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