Vue.js (3) : Vue-router ile bir SPA Uygulaması

Vue.js ve vue.js ekosistemini anlatan yazı dizimize kaldığımız yerden devam ediyoruz. Angular ve reactjs frameworkleri ile bildiğiniz gibi SPA (Single Page Application) uygulamaları geliştirebiliyoruz. Vue ekosistemi aynı imkanı bize  sunmakta mıdır? Evet, vue-router ile bizde bir spa uygulaması geliştirebiliriz.

SPA Nedir ?

Açılımı ingilizce Single Page Application türkçeside tek sayfalık uygulama demektir. Böyle bir uygulama tek  bir html sayfasından oluşmaktadır. Tüm ekranlar, daha doğrusu arayüzler o tek sayfaya yükleniyor. Bu bize ne kazandırıyor diye soracak olursak; tüm sayfalar baştan aşağı yüklenmediği için, network kaynaklarını tüketmeyen, hızlı, performanslı interaktif uygulamalar geliştirebiliyoruz. Ortak olan sayfa bölümleri, javascript ve css dosyaları tekrar tekrar yüklenmediği için, daha az kaynak kullanan, daha hızlı uygulamalar elde etmiş oluruz.

Ayrıca SPA sayesinde desktop uygulamalarına benzer işlevsellikte uygulamalar elde etmiş oluruz. Facebook, instagram, gmail gibi uygulamaların sunduğu işlevselliği, kullanım kolaylığını düşünün. Her uygulamamızın spa olmasına gerek yok, fakat geniş bir kullanıcı kitlesini hedefliyor, işlevsellik, performans ve kullanışlı bir arayüz sunmak istiyorsanız uygulamanızı SPA olarak geliştirmenizde fayda var.

Vue ekosisteminde bir SPA uygulaması geliştirmek için vue-router kütüphanesini kullanmamız gerekiyor.

Basit Bir Örnek

Router yapısını kullanabilmek için vue’daki bileşen (component) mantığını uygulamamız gerekiyor. Yönlendireceğimiz ekranları bileşen olarak tasarlayacağız. Normal bildiğimiz çok sayfalı web uygulamalarında, tek tek oluşturmuş olduğumuz html sayfalar, spa uygulamasında birer bileşene dönüşmüş oluyor. Örneğin UrunListesi.html bir UrunListe bileşenine dönüşüyor. OdemeBilgisi.html, OdemeBilgisi bileşenine dönüşüyor.

Bildiğimiz web uygulamalarında, başka bir sayfaya erişmek için link koyuyorduk. Vue-router’da link ihtiyacını karşılayacak, router-link ifadesini kullanıyoruz.

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <title></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>   
        <script src="https://unpkg.com/vue-router@2.0.1"></script>        
    </head>
    <body>
        <div id="app">
            <h1>Sayfalarda Dolaşın</h1>
            <p>
                <router-link to="/birincisayfa">Birinci Sayfaya Git</router-link>
                <router-link to="/ikincisayfa">İkinci Sayfaya Git</router-link>
            </p>
            <router-view></router-view>            
        </div>
        <script src="app.js"></script>
    </body>
</html>

router-link bileşeni navigasyon için kullanılıyor. to özelliğini kullanarak hangi sayfaya gideceğini belirtmiş oluyoruz. router-link, <a> tag’i olarak işlenir.

router-view bileşeni de sayfaların yükleneceği bölümü ifade ediyor. birincisayfa seçildiğinde, router-view’in içine birinci sayfa yüklenir. Aynı şekilde ikincisayfa seçildiğinde, ikincisayfa yüklenir.

app.js:

var birincisayfa = Vue.component('birincisayfa', {
  template: '<div><h4>Birinci Sayfaya Hoşgeldiniz</h4></div>'
});

var ikincisayfa = Vue.component('ikincisayfa', {
  template: '<div><h4>İkinci Sayfaya Hoşgeldiniz</h4></div>'
});

const routes = [
  { path: '/birincisayfa', component: birincisayfa },
  { path: '/ikincisayfa', component: ikincisayfa }
]

const router = new VueRouter({
  routes // short for routes: routes
})

var app = new Vue({
  el: '#app',
  router
});

Navigasyon yapılacak tüm yollar routes array’inde tanımlanıyor. VueRouter objesine de bu arrayi geçirip, vue modeline de router objesini gönderince, uygulama bir spa uygulaması gibi davranmaya başlayacaktır.

Gelişmiş Bir Örnek

Vue-router, kullanımı ile ilgili bir uygulama yapacağız. Sırasıyla ürün listesinden ürün seçeceğiz, ödeme bilgilerini girereceğiz. Kargo firmasını seçip, onaylayacağız.

routerapp2

Tüm uygulamayı, jquery veya başka bir kütüphane kullanmadan, vue.js ve vue-router.js kütüphanelerini kullanarak yapacağız.

Bu örnekte navigasyonu, router-link bileşeni ile değilde, programatik olarak yapacağız. Önce ürün listesi (prodlist) bileşenini yapalım.

index.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <title></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="style.css" rel="stylesheet">
        <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>   
        <script src="https://unpkg.com/vue-router@2.0.1"></script>
    </head>
    <body>
        <div id="app">
            <div id="baslik">
                <div id="baslikimg">
                    <img src="comp.png"></img>
                </div>
                <div id="baslikyazi">
                    <p>
                        Bilgisayar Malzemeleri (Ucuz ve Kaliteli)
                    </p>
                </div>
            </div>
            <div id="mainarea">
                <router-view :price="totalprice"></router-view>
            </div>
        </div>
        <script type="text/x-template" id="productlisttemp">
            <div id="tablecontainer">
                <h3>{{message}}</h3>
                <table>
                    <thead>
                        <tr>
                            <th>Ürün</th><th>Birim Fiyat</th><th>Adet</th><th>Tutar</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr v-for="(item, index) in productlistarr">
                            <td>{{item.name}}</td><td>{{item.unitprice}}</td>
                            <td><input type="text" v-model='item.quantity' v-on:keyup="keyup(index,$event)"/></td>
                            <td>{{item.price}}</td>
                        </tr>
                    </tbody>            
                </table>
                
                <div id="alttoplam">
                    <span id="totalprice">Total Price: {{totalprice}}</span>

                    <div id="devambtncontainer">
                        <button id="devambtn">Devam (Ödeme Bilgileri) --></button>
                    </div>
                </div>
            </div>       
        </script>
        <script src="app.js"></script> 
    </body>
</html>
style.css
body {
    margin: 0;
    padding: 0;
}

#baslik {
    background-color: azure;
    text-align: center;
    height: 160px;
    color: chartreuse;
    font-family: fantasy;
    font-size: 28px;
    padding-top: 2px;
}

#yanbaslik {
    font-weight: bold;
    color: red;
}

#baslikimg {
    float: left;
    margin-left: 220px;
    text-align: left;
    padding-top: 10px;
}

#baslikyazi {
    text-align: center;
    margin-left: 70px;
    float: left;
    padding-top: 14px;
}

#mainarea {
    text-align: center;
}

#mainarea div {
    text-align: center;
}

#mainarea table {
    margin: 0 auto;
    width: 800px;
}

#mainarea h3 {
    color: #4CAF50;
}

table {
    border-collapse: collapse;
    width: 100%;
}

th, td {
    text-align: left;
    padding: 8px;
}

tr:nth-child(even){background-color: #f2f2f2}

th {
    background-color: #4CAF50;
    color: white;
}

#totalprice {
    font-weight: bold;
    font-size: 1.3em;
    position: relative;
    left: 90px;
}

#totalpricepayment {
    font-weight: bold;
    font-size: 1.3em;
    position: relative;
    left: 0;    
}

#devambtn {
    margin-left: 10px;
}

#devambtncontainer {
    float: right;
}

#backbtncontainer {
    float: left;
}

#alttoplam {
    padding-top: 20px;
    background-color: lavender;    
}

#tablecontainer {
    width: 800px;
    margin: 0 auto;
}
app.js
var productlistdata = {
    message: 'Ürün Listesi',
    totalprice: 0,
    productlistarr : [
            {name: 'klavye', unitprice: 20, price: 0, quantity: 0},
            {name: 'mouse', unitprice: 10, price: 0, quantity: 0},
            {name: 'monitor', unitprice: 150, price: 0, quantity: 0}
    ]
};

var productlist = Vue.component('productlist', {
  template: '#productlisttemp',
  data: function () {
      return productlistdata; 
  },
  methods: {
    keyup: function (index, event) {
      var dataItem = this.productlistarr[index];
      var unitprice = dataItem.unitprice;
      var quantity = dataItem.quantity;
      dataItem.price = unitprice * quantity;

      var totalPriceCalc = 0;    

      for (i = 0; i < this.productlistarr.length; i++) { 
          totalPriceCalc += this.productlistarr[i].price;
      }

      this.totalprice = totalPriceCalc;
      app.$emit('totalpricechanged',this.totalprice);
    },
    gettotalprice: function () {
      return this.totalprice;
    }
  }
});

const routes = [
  { path: '/prodlist', name: 'prodlist', component: productlist }
];

const router = new VueRouter({
  routes // short for routes: routes
});

var app = new Vue({
    el: '#app',
    router,
    data: {
      totalprice:0
    },
    methods: {
      totalpricechanged: function (totalprice) {
        this.totalprice = totalprice;
      }
    }   
});

app.$on('totalpricechanged',function(totalprice) {
  app.totalpricechanged(totalprice);
});

router.push({name: 'prodlist'});


Dikkat edilmesi gereken nokta, router.push ifadesi ile ekran ilk açıldığında, prodlist (ürün listesi) ekranını yüklemiş oluyoruz. Adet girilir girilmez, tutar ve toplam tutar (total price) alanları otomatik olarak güncellenir.

Ödeme Bileşeni

İlk bileşeni yaptıktan sonra, ödeme (payment) bileşenini yapalım. Son kullanıcı ürün listesinden alacağı ürünleri seçtikten sonra, ödeme bilgilerini gireceği bir ekrana geçsin.

index.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <title></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="style.css" rel="stylesheet">
        <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>   
        <script src="https://unpkg.com/vue-router@2.0.1"></script>
    </head>
    <body>
        <div id="app">
            <div id="baslik">
                <div id="baslikimg">

                </div>
                <div id="baslikyazi">
                    <p>
                        Bilgisayar Malzemeleri (Ucuz ve Kaliteli)
                    </p>
                </div>
            </div>
            <div id="mainarea">
                <router-view :price="totalprice"></router-view>
            </div>
        </div>
        <script type="text/x-template" id="productlisttemp">
            <div id="tablecontainer">
                <h3>{{message}}</h3>
                <table>
                    <thead>
                        <tr>
                            <th>Ürün</th><th>Birim Fiyat</th><th>Adet</th><th>Tutar</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr v-for="(item, index) in productlistarr">
                            <td>{{item.name}}</td><td>{{item.unitprice}}</td>
                            <td><input type="text" v-model='item.quantity' v-on:keyup="keyup(index,$event)"/></td>
                            <td>{{item.price}}</td>
                        </tr>
                    </tbody>            
                </table>
                
                <div id="alttoplam">
                    <span id="totalprice">Total Price: {{totalprice}}</span>

                    <div id="devambtncontainer">
                        <button id="devambtn" v-on:click="routetopayment">Devam (Ödeme Bilgileri) --></button>
                    </div>
                </div>
            </div>       
        </script>
        <script type="text/x-template" id="paymenttemp">
            <div id="tablecontainer">
                <h3>{{message}}</h3>
                <table>
                    <tr><td>Kredi Kartı No</td><td><input type="text" v-model='kredikartinfo.no'/></td></tr>
                    <tr><td>Geçerlilik ay/yıl</td>
                        <td>
                            <select v-model='kredikartinfo.gecerlilik'>
                                <option>12/16</option>
                                <option>01/17</option>
                                <option>02/17</option>
                                <option>03/17</option>
                            </select>
                        </td>
                    </tr>
                    <tr><td>Kart Türü</td>
                        <td>
                            <select v-model='kredikartinfo.turu'>
                                <option>Master</option>
                                <option>Visa</option>
                            </select>
                        </td>
                    </tr>
                </table>

                <div id="alttoplam">
                    <span id="totalpricepayment">Total: {{price}}</span>

                    <div id="backbtncontainer">
                        <button id="backbtn" v-on:click="routetoprodlist">Geri (Ürün Listesi)</button>
                    </div>
                    <div id="devambtncontainer">
                        <button id="devambtn">Devam (Kargo Seç) --></button>
                    </div>                    
                </div>                
            </div>
        </script>        
        <script src="app.js"></script> 
    </body>
</html>
app.js
var productlistdata = {
    message: 'Ürün Listesi',
    totalprice: 0,
    productlistarr : [
            {name: 'klavye', unitprice: 20, price: 0, quantity: 0},
            {name: 'mouse', unitprice: 10, price: 0, quantity: 0},
            {name: 'monitor', unitprice: 150, price: 0, quantity: 0}
    ]
};

var productlist = Vue.component('productlist', {
  template: '#productlisttemp',
  data: function () {
      return productlistdata; 
  },
  methods: {
    keyup: function (index, event) {
      var dataItem = this.productlistarr[index];
      var unitprice = dataItem.unitprice;
      var quantity = dataItem.quantity;
      dataItem.price = unitprice * quantity;

      var totalPriceCalc = 0;    

      for (i = 0; i < this.productlistarr.length; i++) { 
          totalPriceCalc += this.productlistarr[i].price;
      }

      this.totalprice = totalPriceCalc;
      app.$emit('totalpricechanged',this.totalprice);
    },
    routetopayment: function () {
      router.push({name: 'payment'});
    },    
    gettotalprice: function () {
      return this.totalprice;
    }
  }
});

var paymentData = {
    message: 'Ödeme',
    kredikartinfo: {
      no: '',
      gecerlilik: '',
      turu: ''
    }
};

var payment = Vue.component('payment', {
  props: ['price'],  
  template: '#paymenttemp',
  data: function () {
    return paymentData;
  },
  methods: {
    routetoprodlist: function () {
      router.push({name: 'prodlist'});
    },
    routetoshipment: function () {
      router.push({name: 'shipment'});
    }    
  }
});

const routes = [
  { path: '/prodlist', name: 'prodlist', component: productlist },
  { path: '/payment', name: 'payment', component: payment},  
];

const router = new VueRouter({
  routes // short for routes: routes
});

var app = new Vue({
    el: '#app',
    router,
    data: {
      totalprice:0
    },
    methods: {
      totalpricechanged: function (totalprice) {
        this.totalprice = totalprice;
      }
    }   
});

app.$on('totalpricechanged',function(totalprice) {
  app.totalpricechanged(totalprice);
});

router.push({name: 'prodlist'});


prodlist bileşenine routetopayment metodu ekledik, bu metod devam (Ödeme Bilgileri) butonuna tıklayınca, ödeme (payment) bileşeninin yüklenmesini sağlayacaktır.

Benzer mantıkla, shipment (kargo) ve approvement (onaylandı) bileşenlerini ekleyerek uygulamamızı tamamlamış oluruz. Dikkat ederseniz sayfalar arasında gidip gelebiliyor, ileri-geri yapabiliyoruz. Komple bir html sayfasının yüklenmesi yerine sadece ilgili bileşenler yükleniyor.

Uygulamanın bitmiş halini github üzerinden inceleyebilirsiniz.

Sonuç

Vue-router ile bir spa web uygulaması geliştirmek çok kolay. Vue.js ile birlikte gelmiş olan bileşen (component) yapısını kavradıktan sonra, son kullanıcıyı yönlendirmek istediğimiz, herhangi bir html sayfasını bileşen (component) haline getirip, dilediğimiz gibi ekranlar arasında dolaştırabiliriz.

Örnekte tüm navigasyon bileşenlerini tek bir dosyada app.js dosyasında tanımladık. Gerçek bir uygulamada böyle bir şey yapmak, doğru olmayacaktır. Bakım ve test anlamında verimsiz bir yapı oluşturacaktır. Bunun yerine yapılması gereken her bileşeni, kendi .vue uzantılı dosyasında tanımlamak ve webpack gibi bir araç kullanarak, uygulamaya entegre etmektir. Bir sonraki vue.js yazımızda böyle bir örnek yapmak üzere hoşçakalın.

 

Yorum Gönder

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Scroll to Top