21 Mart 2022 Pazartesi

VueJs ile Bir Uygulama Yapalım

 Merhaba , VueJs temelleri konusunda çok uzun yazıların ardından işe yarar bir uygulama için kolları sıvayalım.

Bİr sınav uygulaması yapmayı planlıyoruz. Daha önce öğrendiğimiz komponentleri kullanarak uygulamamızı gerçekleştireceğiz. Başlangıç olarak hazırlanan 3 dosyayı indirip bir yere kaydedin lütfen. app.txt , css.txt ve data.txt. Uygulamamızı Vue CLI kullanarak üretelim. Terminalde:

 vue create sinav    

Evet sinav çünkü Vue CLI proje adında Türkçe karakter kabul etmedi. Uygulamayı çalıştıralım test edelim.

 cd sinav                 

 npm run serve       

src/components klasöründeki default komponent dosyasını silelim. App komponenti içini de minimuma düşürelim. 

src/App.vue

<template>
 
</template>

<script>
export default {
  name: 'App',

}
</script>

Şimdi indirdiğimiz dosyalardan app.txt dosyasında görselimiz var. İçindeki html kodun tamamını kopyalayıp template içine yapıştıralım. Stillerin içinde olduğu css.txt dosyasındaki stilleri de ister App komponenti içinde style bölümüne koyalım, ister ayrı bir CSS dosyası yapıp index.html'de sayfaya dahil edelim, her ikisinde de bir fark yok. Fakat eğer ayrı CSS dosyası yapacaksanız, public klasörü içinde main.css adında bir dosya olması genel bir tavsiyedir. 

Taslak görüntümüz ortaya çıkmış olmalı.

Görsel şablonumuza baktığımızda sorular-ctr ve sonuç şeklinde 2 ana bölüm var. Bunlar için 2 ayrı komponent hazırlayacağız. components klasörü içinde Sorular.vue ve Sonuc.vue adında 2 dosyayı ekleyelim. Her iki dosyaya da template ve script bloklarını ekleyelim. 

Sorular bölümünün şablonunu App komponentinden kesip Sorular komponenti şablonuna yapıştıralım.

src/components/Sorular.vue

<template>
    <div class="sorular-ctr">
        <div class="progress">
            <div class="bar"></div>
            <div class="status">3 sorudan 1 tane cevaplandı</div>
        </div>
        <div class="tek-soru">
            <div class="soru">Örnek Soru 1</div>
            <div class="cevaplar">
                <div class="cevap">Örnek Cevap 1</div>
                <div class="cevap">Örnek Cevap 2</div>
                <div class="cevap">Örnek Cevap 3</div>
                <div class="cevap">Örnek Cevap 4</div>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    name: "SorularKomp"
}
</script>

Aynısını Sonuc komponenti için de yapalım.

src/components/Sonuc.vue

<template>
    <div class="sonuç">
        <div class="başlık">Örnek sonuç 1 aldın!</div>
        <div class="açıklama">
            Sonuç hakkında kısa açıklama.
        </div>
    </div>
</template>

<script>
export default {
    name: "SonucKomp"
}
</script>

Vue komponent isim ve dosya isimlerini Türkçe karakter kullanmadan yazdım , sıkıntı olmasın. Şimdi App komponentinde şablonu bu komponentlere göre yapalım. 

src/App.vue

<template>
  <div class="ctr">
    <sorular-komp></sorular-komp>
    <sonuc-komp></sonuc-komp>
    <button type="button" class="reset-btn">Sıfırla</button>
  </div>
</template>

<script>
import SorularKomp from "./components/Sorular.vue"
import SonucKomp from "./components/Sonuc.vue"
export default {
  name: 'App',
  components: {
    SorularKomp,
    SonucKomp
  }
}
</script>

Test edersek çalışmanın aynen devam ettiğini görürüz. Örnek verileri de data.txt dosyasından App komponentine kopyalayalım. 

src/App.vue

  data() {
    return {
      sorular: [
        {
          s: '2 + 2 kaç yapar?',
....

sorular ve sonuçlar adında 2 tane array içinde örnek verilerimizi sıraladık. sorular array içinde nesneler var her nesnede s özelliği ile soru var, cevaplar özelliğinde ise yine bir array içinde cevap şıkları text ve doğru_mu özellikleri ile verilmiş. 

sonuçlar array içinde de olası 2 sonuç göstererek basit tuttuk. min ve max değerleri ile hangi kriterlerde o sonucun gösterileceği belirleniyor. başlık ve açıklama ile de kullanıcıya verilecek mesajlar belirtiliyor. 

Mantık aşağı yukarı belli. Önce kullanıcıya soruları göstereceğiz, kullanıcı cevabını işaretleyecek, verilen cevapları takip edeceğiz ve sorular bitince sonucu göstereceğiz. Cevap girilen soru sayısını saklamak için cevapSayısı adında bir değişken ekleyelim.

src/App.vue

  data() {
    return {
      cevapSayısı: 0,

sorular komponenti cevapSayısı toplam soru sayısından küçükse gösterilecek. Şablonda komponente v-if direktifi ile bunu kodlayalım.

src/App.vue

<template>
  <div class="ctr">
    <sorular-komp v-if="cevapSayısı < sorular.length"></sorular-komp>
    <sonuc-komp></sonuc-komp>
    <button type="button" class="reset-btn">Sıfırla</button>
  </div>
</template>

Elimizdeki örnek verilere göre cevaplanan sayısı 3 olduğunda sorular bitmiş demektir. Bunu tarayıcıda Vue Dev Tools'da değer değiştirip görebiliriz.

Sorular bittiyse sonuç gösterilecek, onu da v-else direktifi ile gösteririz.

src/App.vue

<template>
  <div class="ctr">
    <sorular-komp v-if="cevapSayısı < sorular.length"></sorular-komp>
    <sonuc-komp v-else></sonuc-komp>
    <button type="button" class="reset-btn">Sıfırla</button>
  </div>
</template>

Test edersek cevapSayısı soruların sayısına eşit ya da çok oldu mu, sonuc komponenti görünüyor, sorular komponenti yok oluyor. Şimdi sırada sorular komponentini çalışır hale getirmek var. Basit bir iş değil o yüzden yapılacakları bir yazalım.

  • Tüm sorular üzerinden bir döngü yapılacak ve kullanıcıya gösterilecek.
  • Her soru tek tek görsele gelecek ve takip edilecek.
  • Kullanıcının hangi cevap şıkkını tıkladığı takip edilecek.
  • Cevabın doğruluğuna bakılacak.
  • Cevap doğruysa doğruCevap sayısı arttırılacak.
  • cevapSayısı koşulsuz arttırılacak.
  • Bir sonraki soruya geçilecek.

Başlayalım. sorular verisi App komponentinde bu değeri Sorular komponentine göndereceğiz ki yayınlayabilsin. 

src/App.vue

    <sorular-komp v-if="cevapSayısı < sorular.length"
      :sorular="sorular"
    ></sorular-komp>

,Sorular komponentinde props kısmında bu değeri alalım.

src/components/Sorular.vue

<script>
export default {
    name: "SorularKomp",
    props: [
        "sorular"
    ]
}
</script>

Sıra görsele geldi. Sorular komponenti şablonunda tek-soru class adıyla bir bölüm var. Bu bölümde döngü yaparak her soru için yayınlanmasını v-for direktifi ile sağlayalım. 

src/components/Sorular.vue

<template>
    <div class="sorular-ctr">
        <div class="progress">
            <div class="bar"></div>
            <div class="status">3 sorudan 1 tane cevaplandı</div>
        </div>
        <div class="tek-soru" v-for="soru in sorular" :key="soru.s">
            <div class="soru">{{ soru.s }}</div>
            <div class="cevaplar">
                <div class="cevap" v-for="cevap in soru.cevaplar"
                    :key="cevap.text"
                >{{ cevap.text }}</div>
            </div>
        </div>
    </div>
</template>

cevap div'inden 4 tane koymaya gerek yok onları da veri içinde cevaplar değerinde döngü yaparak koyabiliriz. Ayrıca 4 değil 3 cevabı olan sorumuz da var, döngü ile yapmak daha doğru olur.  Sayfamıza baktığımızda soruların liste halinde görsele geldiğini görebiliriz. 

Vala buraya kadar sıkıntı yok. Şimdi bu soruların sadece sıradaki olanını göstermek ve kullanıcı cevaplarının işlenmesi var. Bir index değerini takip ederek hangi sorunun yayınlanacağına karar vereceğiz. cevapSayısı değeri bu amaçla kullanabiliriz. Bu değeri App komponentinden Sorular komponentine göndererek başlayalım.

src/App.vue

    <sorular-komp v-if="cevapSayısı < sorular.length"
      :sorular="sorular"
      :cevapSayısı="cevapSayısı"
    ></sorular-komp>
    <sonuc-komp v-else></sonuc-komp>

Türkçe karakterler yüzünden VSCode düzgün renklendiremedi çalışmayı engellemez. Sorular komponentinde props bölümünde değeri yukarıdan alalım.

src/components/Sorular.vue

<script>
export default {
    name: "SorularKomp",
    props: [
        "sorular",
        "cevapSayısı"
    ]
}
</script>

Görsel şablonda bu array index değerini de döngüye alıp, sorunun gösterilmesini v-show direktifi ile cevapSayısı eşitliğine bağlayalım.

src/components/Sorular.vue

        <div class="tek-soru" v-for="(soru, index) in sorular"
            :key="soru.s"
            v-show="index === cevapSayısı"
        >

Şimdi sayfayı yenilersek sadece ilk soru görünüyor. Vue DevTools üzerinden cevapSayısı değerini değiştirdikçe gösterilen sorunun da değiştiğini görebiliriz.

Şimdi kullanıcı bu souyu görecek ve cevaplardan birini tıklayacak. Tıklamayı algılayıp sonucu işledikten sonra cevapSayısı değerini arttırarak sonraki soruya geçmeyi sağlayacağız. 

cevap div döngülerinde click olayını işleyen bir cevabıSeç metodu olsun. Önce tıklamayı alalım.

src/components/Sorular.vue

            <div class="cevaplar">
                <div class="cevap" v-for="cevap in soru.cevaplar"
                    :key="cevap.text"
                    @click.prevent="cevabıSeç(cevap.doğru_mu)"
                >{{ cevap.text }}</div>
            </div>

Metoda parametre olarak soru nesnesinin doğru_mu özelliğini de gönderiyoruz ki verilen cevabın doğru mu yoksa yanlış mı olduğunu da bilelim. Doğru cevap sayısını aslında App komponentine bildirmemiz lazım çünkü Sonuc komponenti de bu değere ihtiyaç duyacak. Metodu tanımlarken ilk önce bu değeri App komponentine bildirecek olay bildirimi olarak soru-cevaplandı adında bir olay bildirimi yapalım. 

src/components/Sorular.vue

    methods: {
        cevabıSeç(doğru_mu) {
            this.$emit("soru-cevaplandı", doğru_mu)
        }
    }
}
</script>

Vue bu bildirim için emits opsiyonu tanımlaması da bekler.

src/components/Sorular.vue

<script>
export default {
    name: "SorularKomp",
    emits: ["soru-cevaplandı"],
    props: [

Sırada App komponentinde bu olay bildirimini algılamak var.

src/App.vue

    <sorular-komp v-if="cevapSayısı < sorular.length"
      :sorular="sorular"
      :cevapSayısı="cevapSayısı"
      @soru-cevaplandı="soruCevaplandı"
    ></sorular-komp>

App komponentinde soru cevaplanınca soruCevaplandı adında bir metod çağırılacak. Bunu da tanımlayalım.

src/App.vue

  },
  methods: {
    soruCevaplandı(doğru_mu) {
      if(doğru_mu){
        this.toplamDoğru++
      }
    }
  }
}
</script>

Şimdi de toplamDoğru değerini data() bölümüne ekleyelim.

src/App.vue

  data() {
    return {
      cevapSayısı: 0,
      toplamDoğru: 0,
      sorular: [

Plana göre cevaplanan sorudan sonraki soruya geçmemiz gerekiyor. Bunu da soruCevaplandı metodumuza ilave edelim.

src/App.vue

  methods: {
    soruCevaplandı(doğru_mu) {
      if(doğru_mu) {
        this.toplamDoğru++
      }
      this.cevapSayısı++
    }
  }

Şimdi test edersek sorular cevap tıklandıkça arka arkaya gelecek. Bütün sorular bitince de Sonuc komponenti gösterilecek. 

Sorula kısmı tamam. Şimdi en üstteki Progress Bar'a geçelim. Sorular komponenti görsel şablonunda progress class adıyla bir div elemanı var , hedefimiz orası. İçinde 2 tane div elemanı var. Biri bar görseli için diğeri de üzerindeki bilgi yazısı. Kolay olan yazı ile başlayalım. Örnek değer olarak "3 sorudan 1 tane cevaplandı" yazıyor. Burada gerçek değerleri gösterelim. 

src/components/Sorular.vue

        <div class="progress">
            <div class="bar"></div>
            <div class="status">
                {{ sorular.length }} sorudan {{ cevapSayısı }} tane cevaplandı
            </div>
        </div>

Şimdi her soru cevaplandıkça cevabın doğru ya da yanlış olmasına bakmaksızın rakamın arttığını görürüz. 

Bar için CSS stilimiz hazır oraya bir bakalım. 

src/App.vue

.bar {
  height: 50px;
  background-color: #ff6372;
  transition: all 0.3s linear;
}

Bizim tek yapacağımız cevaplanmış soru sayısının toplam soru sayısı içindeki yüzde değerine göre div genişliğini değiştirmek. Bu amaçla :style bağlaması ekliyoruz.

src/components/Sorular.vue

        <div class="progress">
            <div class="bar"
                :style="{ width: `${cevapSayısı / sorular.length * 100}%` }"
            ></div>
            <div class="status">
                {{ sorular.length }} sorudan {{ cevapSayısı }} tane cevaplandı
            </div>
        </div>

Test edersek sorular cevaplandıkça orantılı olarak bar genişliğinin değiştiğini göreceğiz.

Dikkat ettiyseniz bar ilerlerken animasyon yapıyor. stildeki son satırdaki transition stili bunu yapıyor. 

Sorularla ilgili kısım bitti. Sonuc komponentine geçelim. Sonucu gösterebilmek için App komponentinden Sonuc komponentine sonuçlar ve toplamDoğru değerlerini props yardımıyla gönderelim.

src/App.vue

    <sonuc-komp v-else
      :sonuçlar="sonuçlar" :toplamDoğru="toplamDoğru"
    ></sonuc-komp>

App komponenti gönderdi. Sonuc komponentinde bu değerleri alalım.

src/components/Sonuc.vue

<script>
export default {
    name: "SonucKomp",
    props: [ "sonuçlar", "toplamDoğru" ]
}
</script>

Hangi sonucun yayınlanacağını bulmak için sonuçlar array içinde döngü yaparız ve toplamDoğru sayısı min ve max ile verilen değerlere uygun olan sonucu gösteririz. Yayınlanacak sonucun index değerini sonuçIndex isimli bir hesaplanmış değerde bulalım.

src/components/Sonuc.vue

export default {
    name: "SonucKomp",
    props: [ "sonuçlar", "toplamDoğru" ],
    computed: {
        sonuçIndex() {
        let index = 0;

        this.sonuçlar.forEach((e, i) => {
            if (e.min <= this.toplamDoğru && e.max >= this.toplamDoğru) {
            index = i
            }
        })

        return index
        }
    }
}

Görsel şablondaki örnek yazılar yerine hesapladığımız index'deki başlık ve açıklama'yı yayınlayalım.

src/components/Sonuc.vue

<template>
    <div class="sonuç">
        <div class="başlık">{{ sonuçlar[sonuçIndex].başlık }}</div>
        <div class="açıklama">
           {{ sonuçlar[sonuçIndex].açıklama }}
        </div>
    </div>
</template>

Uygulamanın çalışmasında tek eksik alttaki Sıfırla butonu. Bu butonu tüm sorular cevaplanmadan göstermeyelim. Tıklanınca da sıfırla isimli bir metod içinde cevapSayısı ve toplamDoğru değerlerini sıfır yaparak her şeyin başa dönmesini sağlayalım. 

src/App.vue

    <button type="button"
      class="reset-btn"
      v-if="cevapSayısı === sorular.length "
      @click.prevent="sıfırla"
    >Sıfırla</button>

ve motod da

  methods: {
    soruCevaplandı(doğru_mu) {
      if(doğru_mu) {
        this.toplamDoğru++
      }
      this.cevapSayısı++
    },
    sıfırla() {
      this.cevapSayısı = 0
      this.toplamDoğru = 0
    }
  }

Test edelim ve uygulamamızın düzgün çalıştığını görelim.

Son bir şey daha. Stillerin içinde fade-enter, fade-enter-active ve fade-leave-active class tanımları var. Önceden gördüğümüz kadarıyla bunlar Vue transition komponenti kullanarak animasyon yapmak için kullanılan standart class isimleri. Bunları Sorular ve Sonuc komponentleri arasında geçiş yaparken animasyon olarak kıllanabiliriz. Tek yapacağımız komponentleri bir transition komponenti içine koyup ismini fade yapmak.

src/App.vue

    <transition name="fade" mode="out-in">
      <sorular-komp v-if="cevapSayısı < sorular.length"
        :sorular="sorular"
        :cevapSayısı="cevapSayısı"
        @soru-cevaplandı="soruCevaplandı"
      ></sorular-komp>
      <sonuc-komp v-else
        :sonuçlar="sonuçlar" :toplamDoğru="toplamDoğru"
      ></sonuc-komp>
    </transition>

Animasyon da artık çalışıyor. VueJs işimizi hızlandırdığı kadar, tecrübe kazandıkça bizi daha düzenli program yazmaya da teşvik ediyor. Her şey bir düzen içinde olunca karıştırmadan tek tek uygulama ortaya çıktı. Yapanların ayağına taş değmesin. 

Ne diyelim, sonraki yazılarda buluşmak üzere Kalın Sağlıcakla..



Hiç yorum yok:

Yorum Gönder