10 Mart 2022 Perşembe

Vue3 (Vue.js versiyon-3) Öğreniyorum

 Merhaba,

Ben bu VueJs öğrenme işine bıkmadım, çok takdir ediyorum bu işi. Bir gün mutlaka bir yerde bana para da kazandırır eminim. Bu güne kadar ne öğrendiysem bir projede kullandım. 

İnternette gezerken güzel bir kurs dökümanı buldum. Şimdiye kadar gördüklerime nazaran daha ayrıntılı bir anlatımı vardı. Kafamda takılan sorulara da cevaaplar vardı. Bu öğrendiklerimi sizinle de paylaşayım dedim. 

Hangisi Daha İyi

Angular , Vue , React az çok bildiğim kadarıyla birbirlerine benziyorlar. Amaçları ve yöntemleri arasında farklar neler acaba? 

  • Angular : 
    • Google tarafından geliştirilmiş
    • Lazım olan herşey hazırlanmış , verilmiş
    • Bir şeye ihtiyacın varsa Angular ona ait bir çözüme sahiptir
    • Esnek değil, kuralları neyse uyarsın
  • Vue : 
    • Evan You adında bir geliştirici tarafından geliştirilmiş
    • Diğer ikisinden faydalı olanlar alınmış
    • Temel ihtiyaçlar size verilir (routing, state management vs)
    • Ama mesela formları kendiniz yaparsınız
  • React : 
    • FaceBook tarafından geliştirilmiş
    • Kullanıcıya çoğunlukla sadece görsel kısmı verir
    • Bir çok seçeneği diğer kullanıcı projelerinden elde edebilirsiniz
    • Her şeyi kendiniz yönetmek durumundasınız (örn. httpRequest, stack management)

Tüm bunlar bize bir web uygulaması sonucu verir. Bir görsel , DOM işlemleri , sayfada bazı görüntüleri interaktif olarak değiştirmek. Hangisinin kullanılacağı geliştirici takımına göre değişir. React bilen elemanlar çoksa niye başka birini kullanasınız. Esneklik isteyen çeşitli kütüphaneleri kullanmak isteyen bir takım için React esneklik verecektir. Ama bu esneklik bazen uygulamanın bağımlılıklarını artırır ve ayrıntıda kayboldukça uygulama zayıflamaya başlar. Kalabalık ve tecrübeli bir takıma sahipseniz Angular kullanmak daha iyi olabilir. Çünkü çok kişi aynı yöntemleri kullanmak zorunda, yoksa ipin ucu kaçar. Vue her ikisinden iyi yanları alır. Geliştirme hızınızı arttırır. Kullanışlı araçları vardır. Topluluk tarafından geliştirilen bir proje olduğu için geliştiricilerin üretkenliğini arttırmak üzerine odaklanmıştır. 


İlk VueJs Uygulaması

Bu kadar ön bilgi yeter. Şimdi bir uygulama ile başlayalım. İlk uygulamamız app1 adında bir klasör içinde yer alan index.html , app.js ve main.css dosyalarından oluşuyor. Bu dosyalar içinde şunlar var:

 app1/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>VueJS Öğreniyorum</title>
    <link rel="stylesheet" type="text/css" href="main.css" />
  </head>
  <body>
    <div id="app"></div>

    <script src="app.js"></script>
  </body>
</html>

 app1/main.css

body {
  font-size: 22px;
}

input[type="text"] {
  margin: 10px 0;
  display: block;
}

app.js dosyamızın içi şu anda boş. Ben editör olarak VsCode kullanıyorum. Windows'ta kurduktan sonra app1 klasörü içine gidip boş bir yerde Shit+Sağ Tık yapınca açılan menüde Code ile aç seçeneğini seçtiğinizde klasördeki tüm dosyalar VsCode editöründe açılacaktır. Şimdilik sayfamız boş. İçinde hiç bir şey olmayan bir div içeriyor. Ayrıca app.js JavaScript dosyası ve main.css stil dosyası sayfaya dahil edilmiş. Tarayıcıda index.html dosyasını açarsanız bomboş bir sayfa açılacaktır. 

id değeri app olan div elemanı uygulama kodumuzun içinde dans edeceği bölümü gösteriyor. Uygulamamızın bir VueJs uygulaması olduğunu tarayıcıya anlatmak için güncel VueJs JavaScript dosyasını sayfaya yüklememiz gerekiyor. Sayfa koduna şu satırı ekleyelim:

 app1/index.html

....
    <script src="https://unpkg.com/vue@next"></script>
    <script src="app.js"></script>
  </body>
</html>

VueJs scriptinin dahil edilmesini app.js dosyasından önce yapıyoruz ki uygulama JavaScript'imiz VueJs yüklendikten sonra çalışsın. 

Sıra geldi uygulamamızın JavaScript kodunda VueJs uygulamasını başlatmaya. 

 app1/app.js

Vue.createApp()

Sadece bunu eklemek yeni bir uygulama üretir ama uygulamanın içi boştur. Eğer tarayıcınız Chrome ise ya da Opera olup Chrome eklentisi olarak Vue Devtools  yüklemişseniz sayfayı yenileyince Vue sembolünün renklendiğini göreceksiniz. 

Demek bir şeyleri ateşlemeye başlamışız. Olmazsa tarayıcınızda güvenlik ayarlarında lokal dosyalar için Vue eklentisi çalıştırma kapalı olabilir, kontrol edin.. Vue.createApp() komutu bir Vue nesnesi geri döner. Bu nesnenin bir çok özellikleri vardır. Şimdilik boş bir nesne özellikleri verelim.

Vue.createApp({})

Bu oluşturduğumuz nesneyi sayfamızdaki id değeri app olan div elemanına monte edeceğiz. Biz bir Vue uygulaması nesnesi ürettik ve onu sayfaya istediğimiz yere enjekte edeceğiz. 

Vue.createApp({}).mount("#app")

Uygulamanın monte edileceği yeri belirtmek için standart CSS Seçici yapısını kullanıyoruz. #app parametresi vererek mount() metoduna id değeri app olan elemana monte edeceksin diyoruz. 

Sayfayı yenileyelim ve tarayıcının Geliştirici Araçları'nı açıp Console'da bir hata mesajı olmadığından emin olalım.


Okey , kırmızı bir şey yok uygulama çalışıyor. Elements sekmesini açarsak Vue nesnesinin div elemanına monte edildiğine dair bir kanıt görebiliriz. 

O yazıyı oraya biz koymadık. mount() metodu Vue nesnesini sayfaya monte edince geldi oraya. 

Devam edelim. Sonraki adımımız uygulamamıza veri eklemek. Verileri sayfalarımızda bir şeyler ifade etmek için kullanırız. Mesela kullanıcı adı, ya da resmi gibi şeyler verilerdir. Vue nesnesine veri eklemek için parametreleri içinde data() fonksiyonu tanımlarız. Bu fonksiyon bir nesne geri döner. Arka planda Vue nesnesinden bir veri sorulduğunda data() fonksiyonunu çağırır ve dönen nesne içinde sorgulanan veriyi arar.

 app1/app.js

Vue.createApp({
    data() {
        return {
            ad: "Ümit"
        }
    }
}).mount("#app")

ad değeri Ümit olacak şekilde bir nesne dönecek şekilde data() metodunu tanımladık. Artık uygulamanın bir yerlerinde ad isimli veri sorulursa ne cevap vereceğini biliyor. Şimdi HTML dosyamıza geçelim ve bu veriyi sayfamızda göstermek için kod yazalım. 

 app1/index.html

....
  <body>
    <div id="app">
      {{ ad }}
    </div>
....

Sayfayı yenilersek çok kısa bir süre {{ ad }} yazısı çıktığını sonra Ümit yazdığını görürüz. Önce sayfa olduğu gibi göründü, sonra VueJs dosyası internetten yüklendi ve app.js dosyası da yüklenip çalışınca gitti oraya adı geçen değişken değerini koydu. 

{{ ad }} yapısı Vue için standart bir eşitlik işleme yapısı. Mesela oraya {{ 2 + 2 }} yazsanız sayfaya 4 yazar ya da {{ ad + 2 }} yazsanız sayfaya Ümit2 yazar gibi. Ama burada bir isim verilmişse onu bulunulan bölüme monte edilen Vue nesnesinin data() metodu cevabında arar. {{ ad }} ifadesini uygulamanın monte edildiği div dışında kullanırsak hiç bir anlamı olmaz aynen öylece sayfaya yazılır.

Sayfada bir veri gösterme işini başarmış bulunuyoruz. Bir şey daha, verileri data() metodu içinde tanımlamak zorundayız. JavaScript kodumuzun başka yerinde tanımlanan bir değişkene görselde ulaşmamız mümkün değildir. Mesela data() metodu dışında bir değişken tanımlayalım.

 app1/app.js

const soyad = "Kayacık"

Vue.createApp({
    data() {
....

Görselde de bu değişkene ulaşmaya çalışalım:

 app1/index.html

    <div id="app">
      {{ ad }} {{ soyad }}
    </div>

Sayfayı yenileyince sadece ad görünecek ama soyad görünmeyecektir. Console'a baktığımızda bir bildirim olarak soyad değişkeni kullanıldığı ama Vue oluşum nesnesinde tanımlanmadığına dair bir uyarı mesajı görürüz. 

Geri dönelim ve soyad değerini Vue nesnesi data() metodu içinde tanımlayalım.

 app1/app.js

Vue.createApp({
    data() {
        return {
            ad: "Ümit",
            soyad: "Kayacık"
        }
    }
}).mount("#app")

Şimdi sayfayı yenilediğimizde soyad da görsele gelecektir. Geliştirici araçlarında Vue sekmesini açalışm.


Root komponentine tıkladığımızda data içinde ne değerler olduğunu görebiliriz ve istersek burada değiştirebiliriz.

Birden Fazla Elemanda VueJs

Sayfamıza başka bir div elemanı daha ekleyerek aynı uygulamayı oraya da monte etmesi için aynı id değerini versek çalışmayacaktır. id yerine class kullanarak bunu deneyelim.

 app1/index.html

....
    <div class="app">
      {{ ad }} {{ soyad }}
    </div>

    <div class="app">
      {{ ad }} {{ soyad }}
    </div>
....

Tabi ki app.js dosyasında mount() metodunun parametresini de class işaret edecek şekilde değiştirelim.

 app1/app.js

....
}).mount(".app")

Sayfayı yenilediğimizde sadece ilk bölümde Vue etkileşimi olduğunu görürüz.


Vue nesnesi sadece bir elemana monte edilebilir. Bu eleman da verilen CSS seçiciye uyan ilk eleman olacaktır. Vue nesnesinin monte edildiği elemanın altında bir çok alt eleman bulunabilir ama Root eleman bir tane olmalıdır. İkinci bölümde de bu verileri göstermek istiyorsak benzer verilerle bir Vue nesnesi daha üretip onu da ikinci elemana monte etmemiz gerekir. 

 app1/index.html

    <div id="app">
      {{ ad }} {{ soyad }}
    </div>

    <div id="app2">
      {{ ad }} {{ soyad }}
    </div>

 app1/app.js

Vue.createApp({
    data() {
        return {
            ad: "Ümit",
            soyad: "Kayacık"
        }
    }
}).mount("#app")

Vue.createApp({
    data() {
        return {
            ad: "Hasan",
            soyad: "Yılmaz"
        }
    }
}).mount("#app2")

Artık sayfa düzgün açılır. Vue DevTools'ta artık 2 uygulama göreceğiz.


Normal teamülde bir sayfada bir tane Vue uygulaması olur. Burada amaç biraz nasıl oluru görmek. Normalde bir uygulama ama Root eleman altında bir sürü alt eleman şeklinde tasarım yapılır.

Devam etmeden görselimizden ikinci div elemanını ve script dosyasından da ikinci Vue uygulamasını kaldırıp eskiye dönelim. 


Metodlar

Vue görselimizde değişkenlerden sonra metodların kullanılmasını görelim. Diyelim sayfada ad ve soyad birleşmiş olarak bir değer göstermek istiyoruz ve soyad da büyük harflerle yazılsın istiyoruz. Bu amaçla Vue nesnemizin tanımına tamAd() isimli bir metod şöyle eklenir. 

 app1/app.js

Vue.createApp({
    data() {
        return {
            ad: "Ümit",
            soyad: "Kayacık"
        }
    },
    methods: {
        tamAd() {
            return this.ad + " " + this.soyad.toUpperCase()
        }
    }
}).mount("#app")

Görselimizin div elemanını da şöyle düzenleyelim.

 app1/index.html

    <div id="app">
      {{ tamAd() }}
    </div>

Metod tanımları içinde Vue nesnemizin verilerini kullanırken veri isimleri başına this. eklememiz gerekiyor. Metodlar ileride sayfamızı interaktif hale getirirken buton tıklamaları, input değişimleri vb. bir çok olayları işlerken kullanacağımız şeyler. Şimdilik nasıl ekleneceğini gördük. 


Direktifler

Direktifler Vue uygulamamızın davranışlarını yönlendirebileceğimiz işlemler. Bir örnek olarak sayfayı yenilediğimizde ilk başta kısa bir süre sayfanın ham hali görünüyor, sonrasında VueJs ve app.js çalışınca etkileşimli son hali beliriyor. Bunu bir direktifle önleyebiliriz. Görseli şöyle değiştirelim:

    <div id="app" v-cloak>
      {{ tamAd() }}
    </div>

v-cloak kelimesi tarayıcımıza göre bir özellikdir, ancak VueJs için bu bir direktifdir. VueJs'de tüm direktifler v- ile başlar. v-cloak direktifi VueJs çalışmaya başlayınca bulunduğu elemandan silinir bu kadar. Sayfayı tazelerken geliştirici araçları penceresinde div elemanına bakarsak başlangıçta v-cloak yazısını görürüz ama hemen arkasından silinir. VueJs çalıştığı anda siliniyor. Şimdi küçük bir CSS ile div içeriğinin ham halinde iken görünmemesini sağlayabiliriz. 

 app1/main.css

....
[v-cloak] {
  display: none;
}

Bunun anlamı "bir yerde v-cloak özelliği olan eleman görürsen onu gösterme". Sayfayı tazelersek artık çiğ halin gösterilmediğini sağlamış oluruz. 

Bu basit bir direktifdi ama önümüzde göreceğimiz çok direktifler var.



İki Yönlü Veri Bağlamak

İki yönlü veri bağlamak VueJs'in önemli özelliklerinden. İki yönlü veri bağlama sayesinde JavaScript üzerinden veri değiştirilebildiği gibi HTML görsele konulacak aktif elemanlarla da veri değiştirilebilir. Şimdiye kadarki görselimizin altına bir çizgi çekip ad ve soyad verilerini girmek için 2 input ekleyelim.

 app1/index.html

    <div id="app" v-cloak>
      <p>{{ tamAd() }}</p>
      <hr />

      <label>Ad : </label>
      <input type="text" />

      <label>Soyad : </label>
      <input type="text" />
    </div>

Sayfayı yenileyip formumuza bakalım. 


2 tane input elemanı görünüme geldi. Şimdilik içlerine yazılınca bir etkileri olmuyor. Bu inputları değişkenlere bağlamak için v-model direktifini kullanacağız. 

 app1/index.html

      <label>Ad : </label>
      <input type="text" v-model="ad" />

      <label>Soyad : </label>
      <input type="text" v-model="soyad" />

v-model direktifine değer olarak bağlanacak değişken adını veriyoruz. Şimdi sayfayı yenilediğimizde input alanlarına ad ve soyad değişken değerleri gelecek ve burada yaptığınız değişiklikler de direk etkili olacaktır. 

Dikkatinizi çektiyse sadece değişken değerleri değişmiyor tamAd() metodu da çağrılıyor. Bunların otomatik olmasına reaktiflik deniyor. Bu işlemi kendimiz yapmaya kalksak sadece input değişince paragraf içeriği değiştirmek için şöyle bir JavaScript kod yazmamız gerekecekti.

const input = document.getElementById("ad");

input.addEventListener("keyup", function(){
    const p = document.querySelector("p");
    p.innerText = this.value;
});

Tabi değişken başka yerden değişiyorsa oraya da input eleman içeriğini güncellemek için bir şeyler yazmak lazım... VueJs bizi tüm bu zahmetten kurtarıyor. Geliştirici araçlarına bakarsak v-model direktifi ile input elemanına yerleştirilmiş bir çok olay dinleyici görürüz. 

Bunlar aslında şu ana kadar gördüğümüzden daha fazla şeyler yapılabileceğine işaret gibi. JavaScript üzerinde yapılan değer değişimlerinin etkisini görmek için Konsol kullanacağız ama konsolda Vue nesnemize erişebilmek için JavaScript kodumuzu şu hale getirelim.

 app1/app.js

let vm = Vue.createApp({
    data() {
        return {
            ad: "Ümit",
            soyad: "Kayacık"
        }
    },
    methods: {
        tamAd() {
            return this.ad + " " + this.soyad.toUpperCase()
        }
    }
}).mount("#app")

Şimdi konsolda vm adıyla Vue nesnemize erişebiliriz.

JavaScript ile değer değiştirince paragraf ve input eleman değerleri de hemen güncelleniyor. Yani çift yönlü bir veri bağlaması yapılıyor. 


Dinamik Özellikler 

Bazen HTML özelliklerini dinamik olarak kontrol etmek isteriz. Örneğin sayfamızda Google sitesine bir link olduğunu düşünelim.

 app1/index.html

....
    <div id="app" v-cloak>
      <p>{{ tamAd() }}</p>
      <p><a href="https://google.com" target="_blank">Google</a></p>
....

Web adresi bilgisini bir değişkende saklayalım ve değiştirilebilir olsun.

 app1/app.js

let vm = Vue.createApp({
    data() {
        return {
            ad: "Ümit",
            soyad: "Kayacık",
            url: "https://google.com"
        }
....

href özellik değerinin Vue nesnesinin url değişkenine bağlanmasını istiyoruz. HTML kodunda özellik değerine {{ }} şeklinde bir atama yapamayız. Bunun için v-bind direktifini kullanırız. 

      <p><a v-bind:href="url" target="_blank">Google</a></p>

v-bind:href yazarak href özelliğini Vue nesnesi değişkenine bağlayacağımızı belirtiyoruz. v-bind: arkasından özellik adı şeklinde yazılıyor. Çok kullanılan bir teknik olduğu için bu direktifin kısaltılmış halini yapmışlar. v-bind kelimesini çıkarıp sadece bağlamak istediğimiz özellik adı başına iki nota üstüste karakteri koyarak da aynı işlemi yapabiliriz. 

      <p><a :href="url" target="_blank">Google</a></p>



Sayfada İşlenmemiş HTML Kod Yayınlamak

Güvenlik sebebiyle değişken değerlerini yayınlarken bu değerlere verilebilecek HTML kodlarının elimine edilmesi gerekir. Örneğin ad değeri yerine <a href="http://mysite.com" target="_blank">Ümit</a> yazarlarsa başka bir siteye link olarak görünmesi işimize gelmez. Bizim sayfamızdan abuk gubuk yerlere gönderebilirler. Buna Cross Site Scripting derler (XSS), web sayfamızı çökertmek , başka amaçlar için kullanmak gibi kötü amaçlar olabilir. 

Bir deneme yapalım Vue nesnemize raw_url adında HTML kod içeren bir veri ekleyelim.

 app1/app.js

....
            url: "https://google.com",
            raw_url: "<a href='https://google.com' target='_blank'>Google</a>"
....

Görsele de yeni bir paragraf ekleyerek bu değişkeni gösterelim.

 app1/index.html

....
      <p><a :href="url" target="_blank">Google</a></p>
      <p>{{ raw_url }}</p>
....

Sayfayı yenilediğimizde yazdığımız kodun p elemanı içine kod olarak değil yazı olarak geldiğini görürüz. Aslında olması gereken de budur.

Geliştirici araçlarında bu paragrafı seçim Properties sekmesinde innerHTML değerine bakarsak yazılanlar HTML kod olarak algılanmasın diye < yerine &lt; ve > yerine de &gt; geldiğini görürüz. Bu sayede HTML yerine yazı şeklinde kodun bize görünmesi VueJs tarafından sağlanıyor. Default olarak VueJs veri girerek XSS ataklarını önlüyor. 

Peki ama biz orada HTML kod enjekte etmeyi kendimiz istiyorsak nasıl yapacağız derseniz, bunun için de v-html direktifi kullanılıyor. Görselimizi şu hale getirelim.

 app1/index.html

....
      <p><a :href="url" target="_blank">Google</a></p>
      <p v-html="raw_url"></p>
....

Sayfayı yenilersek paragraf içine değerdeki HTML kodun yerleştiğini görürüz. 

Yalnız bu şekilde sayfamıza veriler üzerinden HTML kod enjekte edilebilecek açıklar bırakılması tavsiye edilmez. Bir şekilde kötü niyetli biri veritabanımıza ulaşarak gireceği verilerle web sitemizin işlevini tamamen değiştirebilir. 


Olayları İzlemek

Tarayıcıda yapılan tuş basmak, mouse tıklamak, mouse'u üzerinde gezdirmek vs. olaylarını yazacağımız metodları kullanarak işleriz. Bunu yapmak için de direktif kullanırız. Vue nesnemize yaş adında ve değeri 20 olan bir değişken daha ekleyelim.

 app1/app.js

....
            url: "https://google.com",
            raw_url: "<a href='https://google.com' target='_blank'>Google</a>",
            yaş: 20
....

Bunu sayfamızda gösterelim

 app1/index.html

....
      <p v-html="raw_url"></p>
      <p>{{ yaş }}</p>
      <hr />
....

Şimdi bu değeri arttırmak ve eksiltmek için 2 tane button elemanı ekleyelim görsele. 

....
      <p>{{ yaş }}</p>
      <button type="button">Arttır</button>
      <button type="button">Azalt</button>
....

Butonlar göründü ama iş yapmıyorlar. Bu button'ların click olayında yani tıklandığında bir metod çalıştırmak istiyoruz. Bu amaçla v-on direktifi kullanılır. 

 app1/index.html

....
      <p>{{ yaş }}</p>
      <button type="button" v-on:click="arttır">Arttır</button>
      <button type="button" v-on:click="yaş--">Azalt</button>
....

Arttırma butonuna arttır isimli metodu çalıştır dedik, azaltma butonunda ise direk JavaScript işlemi yaparak değere müdahale ettik. JavaScript işlemi yaparken this.yaş şeklinde kullanmadığımıza dikkat çekerim. Metod ismi - JavaScript her ikisi de çalışır, ancak arttır metodunu eklememiz gerekiyor.

 app1/app.js

    methods: {
        tamAd() {
            return this.ad + " " + this.soyad.toUpperCase()
        },
        arttır() {
            this.yaş++
        }
    }

Sayfayı yenileyelim butonlarımız çalışmaya başladı. Metod çağırırken parantezler eklemedik, bu opsiyoneldir. Eğer metod çağrısında parametre kullanılmayacaksa parantezlere gerek yoktur. 

Bu olayları takip etme işi de interaktif sayfalarda çok kullanıldığı için buna da bir kısa yöntem yapmışlar. 

      <button type="button" @click="arttır">Arttır</button>
      <button type="button" @click="yaş--">Azalt</button>

v-on: yerine @ karakteri koyarak kestirme yoldan gidilebilir. Olayları kullanacağımız çok yer var. Mesela soyad input elemanında değer kullanıcı tarafından değiştirilince haberimiz olsun istiyoruz. v-model direktifi herşeyi otomatik yapıyor bunun yerine v-bind ile orada değeri gösterip v-on ile tuş olaylarını izlemek için şu hale getiririz. 

      <label>Soyad : </label>
      <input type="text" :value="soyad" @input="soyadGüncelle" />

Bu durumda :value (yani v-bind:value) direktifi sadece değişkenin değerini input elemanına yazarak tek yönlü bağlantı kuruyor. Konsolda vm.soyad değerini değiştirerek test edebilirsiniz. Ama input içinde yapılan değişiklik soyad değişkenine etki etmiyor, bu olay @input ile soyadGüncelle metodunu çağırıyor. Şimdi bu metodu JavaScript kodumuza ekleyelim.

 app1/app.js

....
        arttır() {
            this.yaş++
        },
        soyadGüncelle(event) {
            this.soyad = event.target.value
        }
....

input elemanına girilen bilgiyi almak için metoda parametre olarak event değişkeni giriyoruz. Bu parametre, metodu çağıran olayı işaret eden bir tipik JavaScript olay nesnesi. event.target ise olayı ateşleyen input eleman. event.target.value ile input elemanı değerini alıyor ve soyad değişkenine yazıyoruz. Test edelim. Çalışmada bir fark yok , yine inputta yaptığımız değişiklik soyad değişkenine yazıldı ama bu sefer tamamen bizim kontrolümüzde. Geliştirici araçlarında input elemanı seçip Event Listeners bölümüne bakınca artık sadece input olayının izlendiğini göreceğiz, çünkü biz öyle istedik. İstersek artık soyadı içinde belirli karakterlere kısıtlama getirebiliriz mesela. 



Olay Metoduna Veri Göndermek

Diyelim olay metodunu çağırırken parametre göndermek istiyoruz. Mesela soyadGüncelle() metoduna bir mesaj gönderelim o mesaj da console.log'a yazılsın. Görseli şöyle değiştirelim:

 app1/index.html

      <label>Soyad : </label>
      <input type="text" :value="soyad" @input="soyadGüncelle('Soyad Olayı Tetiklendi')" />

Hatırlarsak daha önce hiç parametre vermediğimiz halde metoda event adında bir parametre otomatik olarak gelmişti. Metodu parametre ile çağırırken , daha önce otomatik eklenen parametreyi manual eklemek zorundayız. yani olay metodu çağrımı şöyle olmalı. 

      <input type="text" :value="soyad" 
             @input="soyadGüncelle('Soyad Olayı Tetiklendi', $event)" />

Burada yazılan parametreyi almak için metodu şöyle düzenleriz.

 app1/app.js

        soyadGüncelle(mesaj, event) {
            event.preventDefault()
            console.log(mesaj)
            
            this.soyad = event.target.value
        }

Sayfayı yenileyince input elemanında her değişikliğimizde konsola mesaj yazılacaktır. event.preventDefault() metodu ise tarayıcı tarafından elemana tanımlı default olay işleme rutinlerini devre dışı bırakmak için her zaman önerilen bir işlemdir, konuyla alakalı değil. Vue bu işlem için bir event modifier tanımlamış. JavaScript kodunda metod içinde satır eklemektense direk görselde şunu yazabiliriz.:

 app1/index.html

      <input type="text" :value="soyad" 
             @input.prevent="soyadGüncelle('Soyad Olayı Tetiklendi', $event)" />

@input.prevent şeklinde yazım metod içine event.preventDefault() yazmakla aynı anlama gelir. Artık JavaScript kodundan o satırı silebiliriz. 


Tuş Takımı Olayları

Bunlar tuş takımında bir tuş kombinasyonuna cevap vermek için üretilen olaylardır. Örnek verelim. Diyelim ad , soyad kısmına bir de ilave orta isim kısmı ekleyelim. Önce data kısmına yeni bir değişken ekliyelim, başlangıç değeri de boş olsun.

 app1/app.js

    data() {
        return {
            ad: "Ümit",
            ortaAd: "",
            soyad: "Kayacık",
....

tamAd() metodunu da bu ilaveyle güncelleyelim:

    methods: {
        tamAd() {
            return this.ad + " " + this.ortaAd + " " + this.soyad.toUpperCase()
        },
....

Sonra görsele ortaAd değeri için bir input elemanı daha ekleyelim. Bu alana bir şey yazılıp <enter> tuşu basılınca değerin kaydedilmesini istiyoruz diyelim. Bir tuş basıldığını algılayan olay adı keyup dır :

 app1/index.html

      <label>Ad : </label>
      <input type="text" v-model="ad" />

      <label>Orta Ad : </label>
      <input type="text" @keyup="ortaAdGüncelle" />

      <label>Soyad : </label>
      <input type="text" :value="soyad" 
             @input.prevent="soyadGüncelle('Soyad Olayı Tetiklendi', $event)" />

Metodumuzu Vue nesnesine ekleyelim:

 app1/app.js

        soyadGüncelle(mesaj, event) {
            console.log(mesaj)
            
            this.soyad = event.target.value
        },
        ortaAdGüncelle(event) {
            this.ortaAd = event.target.value
        }

Bunu test edersek her tuş basıldığında çalıştığını görürüz. Bir şekilde basılan tuşun <enter> tuşu olduğunu bilmemiz lazım. Görselde şu ilaveyi yaparız:

 app1/index.html

      <input type="text" @keyup.enter="ortaAdGüncelle" />

Olası tuş kombinasyonlarının bir listesini VueJs sitesinde bulabilirsiniz. Şimdi test edersek <enter> basılmadan bir değişiklik olmadığını görürüz. 

Key modifiers işi sadece tuş olayı işlemede kullanılmaz. Mesela Arttıt butonunun sadece <Ctrl> tuşu ile birlikte basıldığında çalışmasını istiyoruz.

 app1/index.html 

      <button type="button" @click.ctrl="arttır">Arttır</button>

Sayfayı tazelediğimizde ancak Arttır tuşuna <Ctrl> tuşu basılıyken tıklarsak çalışacaktır. Ancak buna ihtiyacımız yok , o yüzden son yaptığımız değişikliği silelim. 

v-model direktifi de modifier alabilir. Sayfaya yaş değerini değiştirmek için bir input elemanı ekleyelim.

 app1/index.html 

      <p>{{ yaş }}</p>

      <label>Yaş : </label>
      <input type="text" v-model="yaş" />
      <button type="button" @click="arttır">Arttır</button>
      <button type="button" @click="yaş--">Azalt</button>

Sayfayı yenilediğimizde yaş değerinin artık input elemanı üzerinden de değişebildiğini görürüz. Butonlar da hala çalışıyor. Ancaaak biz input elemanına yazı da girebiliyoruz, halbuki buton kodları bizden sayı bekliyor. 


Şimdi butonlardan birine basarsak yaş değerinin NaN olduğunu görürüz, çünkü matematik işlem bizde sayı bekliyordu. Ne olduğunu anlamak için görselde yaş değişkeni veri tipini gösterelim.

 app1/index.html 

      <p>{{ typeof yaş }}</p>

      <label>Yaş : </label>
      <input type="text" v-model="yaş" />

Sayfayı yenileyince başlangıçta veri tipinin number (sayı) olduğunu görürüz.

Ama input içine sayı olmayan karakter ekleyince veri tipi string olacaktır. 

Çözüm olsun diye input elemanı type özelliğini number yapsak?

      <input type="number" v-model="yaş" />

Bu sadece sayı girilmesini sağlar ama mesela 20e-1 de sayıdır 20.3 de sayıdır. Ve bu değerleri girmemize tarayıcı izin verir. Değer girerken bakarsak yine arada veri tipi string'e kayıyor. Bunun yerine Vue direktifi kullanarak kısıtlama yaparsak:

      <input type="text" v-model.number="yaş" />


Vue değerin sayı olan kısmını alacaktır ve string veri tipine kayma olmayacaktır. Her iki yöntem de kullanılabilir. Ama güvenli veri işlemek için her zaman girilen veriyi JavaScript kodunda bir kontrol edip, doğru mudur, yalan yanlış bir şey midir test etmekte fayda vardır. Bazen verilerin doğru girilmesini sağlamak asıl programdan daha fazla yer kaplar. 

2 tane daha v-model modifier'ı var, lazy ve trim. lazy ,  input elemanında veri değişimi yapılırken değil kullanıcı bitirip başka elemana geçtiğinde değerin güncellenmesine yarar, böylece her tuş basıldığında değer değişmez. trim de girilen yazıda birden fazla yan yana gelmiş boşluklar varsa bunları teke düşürür. Denemek için ad input elemanında şu değişikliği yapalım.

 app1/index.html 

      <label>Ad : </label>
      <input type="text" v-model.lazy.trim="ad" />

Şimdi Ad yerine mesela "Ümit ff      ff" yazsanız, yazarken yukarıda isim değerinin değişmediğini görürsünüz. Ne zaman enter basar yada input dışında bir yere tıklarsanız o zaman değer güncellenir. Ama değeri güncellerken de o bir sürü boşluk yerine tek boşluk geldiğini görürsünüz. 



Vue Hesaplanmış Değerler

Hesaplanmış değerler deyince matematik işlem sonuçları, kelimelerin birleşip cümleler elde edilmesi gibi şeyler anlıyoruz. Bir değeri göstermeden önce işlemler yaparak bunların sonucunu göstermek gerekebilir. Örnek uygulamamızda tamAd() metoduna bakarsak aslında bu dediğimiz iş yapılıyor. Gösterilecek olan değer hesaplanarak gösteriliyor. Aslında VueJs'de bu tip değer hesaplamaları Hesaplanmış Değerler bölümünde yapılır. 

Nedenini daha iyi açıklamak için tamAd() metoduna bir ilave yaparak metodun ne zaman çağırıldığını takip edelim.

 app1/app.js

....
        tamAd() {
            console.log("tamAd() metodu çağırıldı")
            return this.ad + " " + this.ortaAd + " " + this.soyad.toUpperCase()
....

Ayrıca daha önce soyadGüncelle() metoduna koyduğumuz console.log() satırını da iptal edelim, artık gerek yok.

....
        soyadGüncelle() {
            this.soyad = event.target.value
        },
....

Tabii ki görselden de mesajı çıkaralım.

 app1/index.html 

....
      <label>Soyad : </label>
      <input type="text" :value="soyad" @input.prevent="soyadGüncelle" />
....

Şimdi sayfayı yenileyelim ve geliştirici araçlarında konsolu açıp mesajlara bakalım. İsimle ilgili bir şeyleri değiştirdiğimizde tamAd() metodu çağırılıyor. yaş değişkenini değiştirdiğimizde de tamAd() metodunun çağırıldığını görüyoruz. İsim değişmedi ama tamAd() yine de çağırılıyor. Bu hesaplanmış değerler kullanmamızın ilk sebebi, metodun sürekli çağırılması büyük çaplı uygulamalarda performans düşmesine sebep olabilir. Uygulamamızdaki diğer metodlar sadece gerekli olduklarında çalışırken tamAd() metodu sürekli çağırılıyor. 

Hesaplanmış değerler kullanarak aynı işlemi yapmak için Vue nesnemize data() ve methods gibi bir özellik daha ekliyoruz, computed.

 app1/app.js

let vm = Vue.createApp({
    data() {
....        }
    },
    methods: {
....
    },
    computed: {
        
    }
}).mount("#app")

tamAd() metodunu methods özelliğinden alıp buraya koyalım sadece mesajı biraz değiştirerek.

    computed: {
        tamAd() {
            console.log("tamAd() hesaplanmış değeri çağırıldı")
            return this.ad + " " + this.ortaAd + " " + this.soyad.toUpperCase()
        }
    }

Bir degörselden metod çağırırken kullandığımız parantezleri kaldıralım, çünkü hesaplanmış değerler metod gibi değil değişken gibi kullanılırlar (data kısmındaki değişkenler gibi).

 app1/index.html 

....
      <p>{{ tamAd }}</p>
....

Şimdi sayfayı yenileyip kontrol edersek sadece tamAd hesaplanmış değerini hesaplarken kullanılan değerlerden biri değiştiğinde hesap yapıldığını ama yaş değişkenine işlem yapılınca hesap yapılmadığını görürüz. 

Biraz açıklamak gerekirse metodlar çağırılınca çalışıyor ve görsel şablonuna metod koyunca o görsel şablonun her yenilemesinde metod çağırılır. O yüzden sayfada ne değişse şablon yenilendiği için metod sürekli çağırılıyor. Hesaplanmış değerler ise Vue tarafından içindeki kullanılan değişkenler takip ediliyor eğer bunlardan biri değişirse tekrar hesaplanıyor, hesaplanmış değer içinde kullanılan değişken değerleri değişmedikçe hesap yapılmıyor. 

Mesela hesaplanmış değer içine yaş değişkenini de koyalım:

 app1/app.js

        tamAd() {
            console.log("tamAd() hesaplanmış değeri çağırıldı")
            this.yaş
            return this.ad + " " + this.ortaAd + " " + this.soyad.toUpperCase()
        }

Şimdi yaş değişkeni değerinde yapılan değişiklikler de hesaplama işlemini çağıracaktır. Gerçi orada bu değer hiç bir şeyi etkilemiyor ama Vue daha bunları çözecek kadar akıllı değil orada yazılmışsa kullanılıyordur deyip hesap yapıyor. Neyse o satırı oradan silelim gerek yok. sadece öğrenmek için koymuştuk.

Geliştirici araçlarında Vue sekmesini açarsak Root nesnesine data'dan sonra computed diye bir bölüm de eklendiğini ve burada tamAd değerini görebiliriz. Enteresan, metod gibi yazılıyor ama data gibi okunuyor sanki. 



Vue İzleyiciler

Vue nesnesi içinde data methods ve computed özelliklerini gördük, sırada yeni biri var watch. İzleyiciler Vue nesnesi içindeki verilerde olan değişiklikleri takip etmek ve değişiklik olunca bir şeyler yapmak amacıyla kullanılırlar. Mesela yaş değişkenini izlemek istiyoruz. Temel yapı şudur:

 app1/app.js

    watch: {
        yaş(yeniDeğer, eskiDeğer) {
            
        }
    }

İzleyiciler tam değer değiştiği anda devreye girer ve eski ve yeni değerlere göre işlem yapılabilir. Basit bir örnek olarak yaş değeri değiştikten 3 saniye sonra başlangıç değerine geri dönsün diye bit timeOut yazalım.

        yaş(yeniDeğer, eskiDeğer) {
            setTimeout(() => {
                this.yaş = 20
            }, 3000)
        }

Test ettiğimizde değeri değiştirdikten 3 saniye sonra tekrar 20 olduğunu görürüz. 



HTML Tag Özeliklerine Bağlama

Yaşasın app2 zamanı. Görsellerimizde stiller üzerinde VueJs'i nasıl kullanacağımız üzerine bir bölüme geldik. app2 adında yeni bir klasör tanımlayalım ve başlayalım. Şunu yapacağız.

 app2/index.html

<!DOCTYPE>
<html>

<head>
  <title>VueJS Öğreniyorum</title>
  <link rel="stylesheet" type="text/css" href="main.css" />
</head>

<body>
  <div id="app">
    <label>
      <input type="checkbox" /> Mor
    </label>

    <div class="circle">
      Hey
    </div>
  </div>

  <script src="https://unpkg.com/vue@next"></script>
  <script src="app.js"></script>
</body>

</html>

 app2/app.js

let vm = Vue.createApp({
}).mount('#app')

 app2/main.css

body{
    font-size: 20px;
    font-family: sans-serif;
}
label{
    margin-bottom: 20px;
    font-size: 20px;
    display: block;
}
select{
    font-size: 20px;
    margin-bottom: 20px;
}
input[type=number]{
    display: block;
    font-size: 20px;
    margin-bottom: 20px;
}
.circle{
    width: 150px;
    height: 150px;
    border-radius: 100%;
    background-color: #45D619;
    text-align: center;
    color: #fff;
    line-height: 150px;
    font-size: 32px;
    font-weight: bold;
}

.mor{
    background-color: #767DEA;
}
.text-siyah{
    color: #424242;
}
.text-turuncu{
    color: #FFC26F;
}

CSS dosyası konumuzla alakalı değil direk buradan kopyalayın. Ama HTML ve JavaScript kodları kendiniz yazmanızı öneririm, öğrenmek için. 

Sayfada bir selam bir de iş yapmayan checkbox var. Bu checkbox ile selam mesajının içinde bulunduğu dairenin rengini değiştirmek istiyoruz. main.css dosyası içinde mor diye bir class stili tanımlanmış. Burada mor-mavi bir arkaplan rengi tanımlanmış. Şimdi mesaj dairesini görselde bulalım, bu class değeri circle olan bir div elemanı olacaktır. Bu elemana mor class değerini VueJs kullanarak dinamik olarak ekleyip çıkarabilmek için şu ilaveyi yapalım. 

 app2/index.html

    <div class="circle" :class="{ mor: morSeçili }">
      Hey
    </div>

:class özelliği önceden gördüğümüz gibi v-bind:class kısaltılmış hali. Elemanda 2 tane class olması VueJs tarafında karışıklık yapmaz, onları alır tek olarak yayınlamasını yapar. class değerini dinamik olarak verirken key-value şeklinde bir nesne veriyoruz. key değeri dinamik olarak eklenmesini istediğimiz class adı, value true değer aldığında bu class elemana eklenir , tersi olunca silinir. 

morSeçili değişkeni tanımlı değil, JavaScript kodumuza gidip ilaveyi yapalım.

 app2/app.js

let vm = Vue.createApp({
    data() {
        return {
            morSeçili: false
        }
    }
}).mount('#app')

Hemen tarayıcıda geliştirici araçları Vue sekmesinde morSeçili değerini değiştirerek etkisini inceleyebilirsiniz. Şimdi v-model ile checkbox elemanını morSeçili değişkenine bağlayalım.

 app2/index.html

    <label>
      <input type="checkbox" v-model="morSeçili" /> Mor
    </label>

checkbox da çalışmaya başladı.

Pek de mor değilmiş, baya bi mavi bu renk. Birden çok sayıda class için bu dinamik yapıyı kullanırsak HTML kodumuz karışmaya başlar. Bu gibi durumlarda hesaplanmış değerleri kullanabiliriz. Mesela bu daireye yapılacak class müdahalelerini circle_classes adında bir hesaplanmış değerde toplayalım. 

 app2/app.js

let vm = Vue.createApp({
    data() {
        return {
            morSeçili: false
        }
    },
    computed: {
        circle_classes() {
            return { mor: this.morSeçili }
        }
    }
}).mount('#app')

Sadece this. kelimesini eklemeyi unutmayalım. Sonra görsel kodumuzu bu hesaplanmış değeri kullanacak şekilde değiştirelim.

 app2/index.html

    <div class="circle" :class="circle_classes">
      Hey
    </div>

Böyle lojiği olan şeyleri JavaScript içinde kullanmak daha doğru. Şimdi class değerlerine bir ilave daha yapalım. CSS dosyamızda text-siyah ve text-turuncu diye yazı rengi değiştiren 2 class stili tanımlı. Bunları kullanarak selam yazımızın rengini değiştirelim. Bu amaçla görsele içinde Beyaz, Siyah ve Turuncu seçenekleri olan bir dropdown select elemanı ekleyelim.

 app2/index.html

    <label>
      <input type="checkbox" v-model="morSeçili" /> Mor
    </label>

    <select>
      <option value="">Beyaz</option>
      <option value="text-siyah">Siyah</option>
      <option value="text-turuncu">Turuncu</option>
    </select>

Beyaz için bir value değeri vermiyoruz çünkü zaten beyaz değiştirmezsek beyaz kalır. Önce select elemanını seçilenRenk adlı bir değişkene bağlayalım.

 app2/app.js

    data() {
        return {
            morSeçili: false,
            seçilenRenk: ""
        }
    },

Sonra v-model ile select elemanına bağlayalım.

    <select v-model="seçilenRenk">

Denemek için selam bölümü dinamik class değerine şimdilik seçilenRenk değişkenini bağlayalım

 app2/index.html

    <div class="circle" :class="seçilenRenk">
      Hey
    </div>

Test edelim:


Yazı rengi seçime göre değişiyor. Ama tabi ki checkbox artık çalışmaz oldu. Bir'den fazla class olasılığı varsa görselde bunları bir array içinde ifade edebiliriz. Şimdi div elemanı kodunu şöyle düzenleyelim:

 app2/index.html

    <div class="circle" :class="[seçilenRenk, circle_classes]">
      Hey
    </div>

Şimdi her ikisi de çalışıyor. Array şeklinde kullanmak ya da hepsini circle_classes nesnesi içine yerleştirmek, işinize hangisi gelirse onu kullanabilirsiniz.




Direk Eleman Stilini Bağlamak

Bu sefer bir elemanın style özelliğini dinamik hale getireceğiz. Bazen class tanımları ve CSS yerine direk eleman stiline müdahale etmek isteyebiliriz. Diyelim mesaj dairesinin boyutunu değiştirmek istiyoruz. 3 tane değiştirmemiz gereken özellik var. width ve height hemen aklımıza gelen 2 tane. Ama denemek için geliştirici araçlarında mesela bu 2 özelliğe 200 px değeri verince mesaj yazısı yerinden kayıyor. 

Yazının da ortalaması için elemanın line-height özelliğini de 200 px yapmalıyız, işte 3. özellik bu. Şimdi görselde bir input elemanı ile bu boyutu kontrol etmek için önce JavaScript kodumuza değişken ekleyelim.

 app2/app.js

    data() {
        return {
            morSeçili: false,
            seçilenRenk: "",
            boyut: 150
        }
    },

Zaten başlangıçta da CSS dosyasından boyut 150 px olarak verilmiş. Görsele input elemanını ekleyip bu değişkene v-model ile bağlayalım. 

 app2/index.html

    </select>

    <input type="number" v-model="boyut" />

    <div class="circle" :class="[seçilenRenk, circle_classes]">

style özelliğine değer verirken de class gibi bir nesne gönderiyoruz. Ama class özelliğinde class_adı: koşul şeklindeydi buradaysa direk stil değerlerini vereceğiz. Kafalar karışmasın. 

 app2/index.html

    <div class="circle" :class="[seçilenRenk, circle_classes]"
       :style="{ width: boyut + 'px', height: boyut + 'px', 
         'line-height': boyut + 'px' }">
      Hey
    </div>

'line-height' yazarken tırnak içine aldık çünkü bu bir nesne ve nesne özellik adında tire işareti olamaz, onu JavaScript çıkarma işlemiyle karıştırır. Alternatif olarak (ve tabi ki CSS e değil VueJs'e ait bir özellik olarak) camelCase şeklinde yani lineHeight yazarak tırnak olmadan da yazabiliriz. 

Bir stil daha ayrıca değiştirmek istersek bunu da class özelliği gibi array içinde başka bir nesne olarak verebiliriz.

 app2/index.html

    <div class="circle" :class="[seçilenRenk, circle_classes]"
       :style="[
          { width: boyut + 'px', height: boyut + 'px', lineHeight: boyut + 'px' },
          { transform: 'rotate(36deg)' }
          ]">
      Hey
    </div>

İkinci stil de div elemanını 36 derece döndürecektir.




Koşullu Görsel Yayınlamak

Yeni bir uygulamaya geçelim. İçinde boş bir Vue uygulaması var. app3 adında bir klasör tanımlayıp içine dosyalarımızı koyalım.

 app3/index.html

<!DOCTYPE>
<html>

<head>
  <title>VueJS Öğreniyorum</title>
  <link rel="stylesheet" type="text/css" href="main.css" />
</head>

<body>
  <div id="app"></div>

  <script src="https://unpkg.com/vue@next"></script>
  <script src="app.js"></script>
</body>

</html>

 app3/app.js

let vm = Vue.createApp({
}).mount('#app');

 app3/main.css

body{
    font-size: 20px;
    font-family: sans-serif;
}
select{
    display: block;
    margin-top: 15px;
    font-size: 20px;
}

Koşullu görsel yayınlama bir elemanı belli koşullara göre yayınlamayı ya da silmeyi anlatır. Eğer koşul doğruysa eleman sayfaya eklenir, eğer koşul doğru değilse eleman sayfaya eklenmişse bile silinir. Önceki bölümde elemana class ve style eklemesini öğrendik, bu teknikle CSS stillleri kullanarak elemanı görünmez yapabiliriz. Koşullu görsel yayınlama anacıyla VueJs v-if direktifini kullanırız. 

Mesela mod değişkeni değeri 1 iken bir paragrafı göstermek istersek:

  <div id="app">
    <p v-if="mod == 1">v-if direktifini gösterir</p>
  </div>

Tabi ki mod değişkenini Vue nesnesi data kısmına eklemeliyiz.

 app3/app.js

let vm = Vue.createApp({
    data() {
        return {
            mod: 1
        }
    }
}).mount('#app');

Başlangıç olarak 1 değeri verdiğimiz için paragraf görünecektir. Geliştirici araçlarına bakarsak paragraf elemanı standart bir eleman gibi oraya gelmiş görürüz. 


Konsola geçip mod değerini değiştirmek için mesela vm.mod = 2 girersek paragraf yok olur.

Paragraf bir şekilde gizlenerek görünmez yapılmadı tamamen görselden çıkarıldı. Ama dikkat ederseniz tam görselin gelmesi gereken yerde bir küçük yorum belirdi. Bu geliştiriciye orada v-if direktifi ile koşullu yayınlanan bir eleman olduğunu gösteriyor.

v-else-if ve v-else direktiflerini de görmek için görsele 2 paragraf daha ekleyelim.

 app3/index.html

    <p v-if="mod == 1">v-if direktifini gösterir</p>
    <p v-else-if="mod == 2">v-else-if direktifini gösterir</p>
    <p v-else>v-else direktifini gösterir</p>

v-else-if direktifi öncesinde gelen v-if ya da v-else-if koşulları true sonuç vermediyse çalışır. 

Araya mod == 3 , mod == 4 gibi başka v-else-if elemanları da ekleyebiliriz istersek. Ama v-if her bloğun başında  bir tek olur. Yeni bir v-if direktifi koymak kendinden önceki bloğun bittiğini de gösterir. 

v-else direktifi ise bloğun başındaki v-if direktifinden oraya gelene kadar tüm v-if ve v-else-if koşulları false sonuç verirse çalışır. Koşulu yoktur öncekiler geçersiz se v-else olan eleman görselde yayınlanır. 

v-if bloğu içine direktifi olmayan ya da v-if yapısından başka direktifi olan bir eleman gelirse hata oluşur. Mesela araya koşulsuz bir paragraf elemanı daha ekleyelim.

    <p v-if="mod == 1">v-if direktifini gösterir</p>
    <p>Extra</p>
    <p v-else-if="mod == 2">v-else-if direktifini gösterir</p>
    <p v-else>v-else direktifini gösterir</p>

Sayfayı yenileyince konsolda hata mesajını görürüz.

Yani diyor ki hacı abi sen buraya v-else-if yazdın v-else yazdın ama ben bir önceki elemanda bulunması gereken v-if koşulunu göremiyorum. Böyle olunca sadece en üstteki v-if koşulu çalışır, kopmadan sonra gelen v-else-if ve v-else direktifleri çalışamayacağı için bu elemanlar hep görünür. Neyse silelim extra paragrafı. 

Bir şey daha paragraf falan kullanmak diye bir zorunluluk yok. Başka elemanlar da kullanılabilir. 2. paragrafı h3 elemanı yapalım mesela.

    <p v-if="mod == 1">v-if direktifini gösterir</p>
    <h3 v-else-if="mod == 2">v-else-if direktifini gösterir</h3>
    <p v-else>v-else direktifini gösterir</p>

Bir şey değişmez bu sefer de h3 elemanı görünür ya da görünmez. Son olarak sayfaya bir seçim ekleyelim de konsoldan mod değeri değiştirmeye uğraşıp durmayalım.

 app3/index.html

  <div id="app">
    <p v-if="mod == 1">v-if direktifini gösterir</p>
    <h3 v-else-if="mod == 2">v-else-if direktifini gösterir</h3>
    <p v-else>v-else direktifini gösterir</p>

    <select v-model="mod">
      <option value="1">v-if</option>
      <option value="2">v-else-if</option>
      <option value="3">v-else</option>
    </select>
  </div>

Bu özellik VueJs'in en kuvvetli özelliklerinden biridir ve çok kullanılır. 

Peki bir'den fazla elemanı aynı koşulda yayınlamak istersek? Hemen akla gelen , elemanları bir div içine toplarsın ve div özelliklerine Vue direktifini koyarsın. Mesela:

 app3/index.html

    <p v-if="mod == 1">v-if direktifini gösterir</p>
    <div v-else-if="mod == 2">
      <p>Extra eleman</p>
      <h3>v-else-if direktifini gösterir</h3>
    </div>
    <p v-else>v-else direktifini gösterir</p>

Bu çalışır, ama dökümanda yapı bozuldu. Ortada bir şey yokken hakim olma kolay belki ama sayfada yüzlerce eleman olunca stilller kodlar arasında çuvallama ihtimali artar. Ama div yerine bir template elemanı içine koyarsak, Vue bunu eleman olarak kabul etmediği için yapı bozulmayacaktır. 

 app3/index.html

    <p v-if="mod == 1">v-if direktifini gösterir</p>
    <template v-else-if="mod == 2">
      <p>Extra eleman</p>
      <h3>v-else-if direktifini gösterir</h3>
    </template>
    <p v-else>v-else direktifini gösterir</p>

Çalıştırıp geliştirici araçlarında incelersek template elemanının yayınlanmadığını ama içindekilerin yayınlandığını göreceğiz. 



v-show direktifi

Eğer elemanın yerinde kalmasını ama CSS ile gizlenmesini de isteyebiliriz. style ya da class bağlama ile bunu çözebiliriz, ama Vue bize daha kolay bir yol sunuyor. Sayfaya bir italik yazı ekleyip v-show direktifi ile görünmesini sağlayalım. 

 app3/index.html

    <p v-else>v-else direktifini gösterir</p>

    <i v-show="mod == 1">İtalik yazı</i>

Test ettiğimizde mod değeri 1 iken yazının göründüğünü ama diğer değerlerde görünmediğini çalışmasının aynı olduğunu zannetsek de aynı değildir. Geliştirici araçlarında elements sekmesini açarsak elemanın hala yerinde durduğunu sadece stiline display: none; yazılarak görünmez yapıldığını fark ederiz.

v-show direktifi template ile çalışmaz. Eksisi sayfa yapılırken görünen görünmeyen herşeyin yüklenmesinden dolayı olabilecek zaman kaybıdır. Artısı çok hızlı değişebilmesidir. Ayrıca v-show'un v-else gibi bir seçeneği yoktur. 



Listeleri Yayınlamak

Bu bölümde listelerin nasıl yayınlanacağını göreceğiz. Yeni bir uygulama başlatıyoruz. Listeler birçok şey için kullanılır: menüler, kullanıcılar, gönderiler vs. Birçok uygulamada lazım olur. Listeler yayınlanırken liste elemanlarını birer birer alıp aynı görselleri tekrarlayarak yayınlarız. Şimdi başlangıç için app4 klasörü içinde şu dosyaları yazalım.

 app4/index.html

<!DOCTYPE>
<html>

<head>
  <title>VueJS Öğreniyorum</title>
  <link rel="stylesheet" type="text/css" href="main.css" />
</head>

<body>
  <div id="app"></div>

  <script src="https://unpkg.com/vue@next"></script>
  <script src="app.js"></script>
</body>

</html>

 app4/main.css

body{
    font-size: 20px;
    font-family: sans-serif;
}
ul{
    margin-bottom: 10px;
}

 app4/app.js

let vm = Vue.createApp({
    data() {
        return {
            kuşlar: ['Güvercin', 'Kartal', 'Karga', 'Papağan'],
            üyeler: [
                { isim: 'Ümit', yaş: 56 },
                { isim: 'Dilek', yaş: 57 },
                { isim: 'Ahmet', yaş: 45 }
            ]
        }
    }
}).mount('#app');

data bölümünde string array olarak kuşlar ve nesne array olarak üyeler verileri listeler halinde verilmiş. İlk bakışta görünen 2 tane array elemanları üzerinde döngüler yapacağız. Bu döngülerin birinde de nesnelerin özellikleri üzerinden döngüler yapacağız. 

Kuşlardan başlayalım. Sayfamıza içinde bir tek list-item olan bir unordered-list elemanı yerleştirelim. list-item elemanını v-for direktifi ile kuşlar array değerleri üzerinden döngü yapalım. 

 app4/index.html 

  <div id="app">
    <ul>
      <li v-for="kuş in kuşlar">
        {{ kuş }}
      </li>
    </ul>
  </div>

Buradaki kuş in kuşlar parametresi v-for direktifine şunu anlatır. Kuşlar array içindeki her eleman için ekrana bu elemanı koy (li elemanı) kuş değişkeni değeri de o andaki array eleman değeri olsun. Yani ilk turda kuş="Güvercin" olacak ikinci turda kuş="Kartal" olacak vs. her seferinde de görsele bir li elemanı eklenecek. Sonuç test edersek şu olur:

Sadece içerikle sınırlı değiliz, kuş değişkenini döngü içinde özelliklerde de kullanabiliriz.

      <li v-for="kuş in kuşlar" :class="kuş">
        {{ kuş }}
      </li>

gibi. Array elemanının değerini bilmek kadar index'ini bilmek de önemli. Bu veriler normalde böyle sabit olmayacak, liste değişecek , elemanlar eklenecek çıkarılacak vs. Bu işler hep index kullanarak yapılacak. 

 app4/index.html 

      <li v-for="(kuş, index) in kuşlar" :class="kuş">
        {{ kuş }} - {{ index }}
      </li>

Değişken isimi ve index (index yerine başka şey de denebilir a, inx, ix falan farketmez) bir parantez içinde bu sırada verilir. Bu durumda index değişkeninde array elemanının indexi yer alır.

Şimdide nesne özellikleri üzerinden nasıl döngü yapacağımızı görelim. Bir yatay çizgi çekip ikinci bir unordered-list elemanı yerleştirelim.

 app4/index.html 

...
    </ul>

    <hr />

    <ul>
      <li v-for="üye in üyeler">
        <div>{{ üye.isim }}</div>
        <div>{{ üye.yaş }}</div>
      </li>
    </ul>

Aynı JavaScript nesne notasyonu gibi nokta ile nesne özelliklerine erişiyoruz tabi. 

2 tane div yerine bir tane div koyup nesne içinde döngü yapmak şöyle olur.

      <li v-for="üye in üyeler">
        <div v-for="(value, key, index) in üye">
          {{ key }}: {{ value }} - Index: {{ index }} 
        </div>
      </li>

Utanmasak HTML içinde kod yazacağız, bu ne? Test edelim.

value, key, index olamayıp value, key ya da sadece value de yazarız ama value, index olmaz sırayı bozamayız. 




index bilmecesi

İlerlemeden önce üzerinde durulması gereken bir nokta var. İlk döngüye geri dönelim. Kullanıcının mesela verileri filtrelemesini isteyebiliriz. Bazen listeden eleman silinmesi ya da yeni eleman eklenmesi gerekebilir. Bu durumda her bir elemanı takip etmek hangisine işlem yapacağımızı bilmek gerekir. index değerini kullanabilir miyiz acaba? 

Örnek olarak listedeki son kuş olan Papağanın index değeri 3, bunun gerçekten Papağan değerine sabit bir index olup olmadığını şöyle test edelim. Konsolu açalım ve vm.kuşlar.shift() komutu girelim. Bu listede ilk eleman olan Güvercin değerini silecektir. Sayfa şöyle görünür.


 Gördüğümüz gibi Papağanın index değeri 2 olarak değişti. Gerçi değişmesine rağmen hala index değeri 2 olan bir tek kuş var listede. mesela vm.kuşlar[2] = "Serçe" komutu girersek Papağan değerinin Serçe olarak değiştiğini görürüz. Bu sağlıklı çalışıyor. Anacak karmaşık listelerde , karmaşık görsellerde bu index değeri ile işlem yapmakta VueJs zorlanabilmekte. Bunu daha iyi anlatabilmek için başka bir uygulama deneyelim. 


Vue Görselde :key özelliği

app5 adında yeni bir klasör içinde şu dosyaları oluşturalım.

 app5/index.html 

<!DOCTYPE>
<html>

<head>
  <title>VueJS Öğreniyorum</title>
  <link rel="stylesheet" type="text/css" href="main.css" />
</head>

<body>
  <div id="app">
    <button type="button" class="move">Aşağı Gönder</button>
    <div class="card">
      <h3>İsim</h3>
      <p>Mesaj</p>
    </div>
  </div>

  <script src="https://unpkg.com/vue@next"></script>
  <script src="app.js"></script>
</body>

</html>


 app5/app.js

let vm = Vue.createApp({
  data() {
    return {
      üyeler: [
        {
          isim: 'Ümit',
          mesaj: 'Merhaba Dünya!'
        },
        {
          isim: 'Dilek',
          mesaj: 'Pastayı çok severim.'
        },
        {
          isim: 'Hasan',
          mesaj: 'Uçmak çok eğlenceli!'
        }
      ]
    }
  }
}).mount('#app')


 app5/main.css

body {
  font-size: 20px;
  font-family: sans-serif;
  padding-top: 10px;
  background: #e6ecf1;
}

#app {
  text-align: center;
}

#app button {
  background-color: #899ff4;
  border-color: transparent;
  color: #fff;
  font-size: 20px;
  padding: 10px;
  margin-bottom: 10px;
}

#app .card {
  width: 400px;
  margin: 15px auto;
  padding-bottom: 15px;
  background-color: #fff;
  box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
  color: #4a4a4a;
  display: block;
  padding: 1.25rem;
}

#app input:focus {
  outline: 0;
}

#app input {
  font-weight: bold;
  width: 100%;
  font-size: 1em;
  padding: 5px 10px;
}

Bu sefer biraz fazla stil var. Sayfayı tarayıcıda açıp bakalım. 


Bir buton ve altında bir bölüm var. Bu bölümden döngü yapıp her birinde üye ismi ve o üyenin mesajını yayınlamayı düşünüyoruz. 

 app5/index.html 

    <div class="card" v-for="üye in üyeler">
      <h3>{{ üye.isim }}</h3>
      <p>{{ üye.mesaj }}</p>
    </div>

Lite sayfaya gelmiş olmalı. 

Üstte bulunan butona tıklanınca ilk üye bilgilerinin en alta gitmesini istiyoruz. taşı adında bir metod ile VueJs bunu yapacak olsun. Görselden başlayalım.

 app5/index.html 

    <button type="button" class="move" @click="taşı">

Metodu JavaScript kodumuza ekleyelim.

 app5/app.js

....
  },
  methods: {
    taşı() {
      const ilki = this.üyeler.shift()
      this.üyeler.push(ilki)
    }
  }
}).mount('#app')

Önce listenin ilk elemanını siliyor ve ilki adında bir değişkene saklıyoruz. Sonra bunu listenin en sonuna push() metodu ile ekliyoruz. 

Sayfayı yenilediğimizde butonun istediğimiz gibi çalışmakta olduğunu görürüz. Basit görselde sağlıklı çalışıyor. Görsele interaktif bir eleman olarak input elemanı ekleyelim. 

 app5/index.html 

    <div class="card" v-for="üye in üyeler">
      <h3>{{ üye.isim }}</h3>
      <p>{{ üye.mesaj }}</p>
      <input type="text" />
    </div>

input elemanlarını henüz hiç birşeye bağlamadık. Ama diyelim başka bir bilgiyi de bölgeye eklemek için böyle bir ihtiyaç duyduk. Test edelim bakalım ne olacak? input elemanına herhangi bir şey yazıp butona tıkladığımızda yazılanın aşağı taşınmadığını görürüz. 

Örnek vermek gerekirse bu bir mesajlaşma olsa ve Ümit kişisine mesaj yazsak artık mesaj Dilek kişisinde görünüyor. Buradaki asıl sorun bilgiye ait div elemanı kaymıyor, elemanların içindeki bilgi kayıyor. Bizim div elemanını da ait olduğu bilgiyle beraber hareket ettirmemiz gerekiyor. VueJs bu sıkıntıyı çözmek için bize :key özeliiiğini öneriyor. Bu özelliği mesela isim'e bağlayalım.

 app5/index.html 

    <div class="card" v-for="üye in üyeler" :key="üye.isim">
      <h3>{{ üye.isim }}</h3>
      <p>{{ üye.mesaj }}</p>
      <input type="text" />
    </div>

Bu özellik elements sekmesinde görünmüyor, ama artık VueJs o div elemanının hangi bilgiye ait olduğunu takip edebiliyor. 

İş büyümeye başladıkça buna benzer sıkıntılarla başınız ağrımasın istiyorsanız VueJs bize listeleri yayınlarken her zaman :key özelliği kullanmamızı tavsiye ediyor. 



Öğrendiklerimizden Bir Uygulama Yapalım

Şimdiye kadar gördüklerimizi kullanarak bir pratik yapalım. app6 klasörümüzü oluşturup şu dosyaları ekleyelim. 

 app6/index.html 

<!DOCTYPE>
<html>

<head>
  <title>CSS3 Perspective Playground</title>
  <link rel="stylesheet" type="text/css" href="main.css" />
</head>

<body>
  <div id="app">
    <h2>CSS3 Perspective Playground</h2>
    <main>
      <section class="settings">
        <div class="settings-container">
          <label>perspective: 0px;</label>
          <input type="range" min="0" max="999" />

          <label>rotateX: 0deg; </label>
          <input type="range" min="-180" max="180" />

          <label>rotateY: 0deg; </label>
          <input type="range" min="-180" max="180" />

          <label>rotateZ: 0deg; </label>
          <input type="range" min="-180" max="180" />

          <button type="button">Sıfırla</button>
          <button type="button">Kopyala</button>
        </div>
      </section>
      <section class="output">
        <div class="box-container">
          <div class="box"></div>
        </div>
      </section>
    </main>

  </div>

  <css-doodle>
    :doodle {
    @grid: 1x3 / 100vmax;
    position: absolute;
    top: 0; left: 0;
    z-index: 0;
    }

    @size: 100% 150%;
    position: absolute;

    background: @m(100, (
    linear-gradient(transparent, @p(
    #FFFDE1@repeat(2, @p([0-9a-f])),
    #FB3569@repeat(2, @p([0-9a-f]))
    ))
    @r(0%, 100%) @r(0%, 100%) /
    @r(1px) @r(23vmin)
    no-repeat
    ));

    will-change: transform;
    animation: f 50s linear calc(-50s / @size() * @i()) infinite;
    @keyframes f {
    from { transform: translateY(-100%) }
    to { transform: translateY(100%) }
    }
  </css-doodle>

  <script src="https://unpkg.com/css-doodle@0.6.1/css-doodle.min.js"></script>
  <script src="https://unpkg.com/vue@next"></script>
  <script src="app.js"></script>
</body>

</html>


 app6/main.css

html {
  box-sizing: border-box;
  width: 100%;
  height: 100%;
}

*,
*:before,
*:after {
  box-sizing: inherit;
  padding: 0;
  margin: 0;
}
body {
  font-family: sans-serif;
  height: 100%;
  margin: 0;
  background: #261c33;
  overflow: hidden;
  display: flex; 
  align-items: center; 
  justify-content: center 
}

h2 {
  color: #8d81f3;
  text-align: center;
  font-size: 40px;
  margin: 20px;
}
main {
  display: flex;
  justify-content: space-around;
  align-items: center;
  height: 420px;
  max-width: 768px;
  margin: 0 auto;
  font-family: monospace, sans-serif;
  font-size: 22px;
}
main label{
  display: block;
}
main input[type="range"] {
  display: block;
  margin-bottom: 10px;
  width: 200px;
}
section.settings {
  width: 50%;
  z-index: 2;
}
.box-container {
  padding: 50px;
  border: 1px solid #8d81f3;
}
.box {
  width: 150px;
  height: 150px;
  background: #8d81f3;
}

button {
  background-color: #8d81f3;
  color: #fff;
  display: inline-block;
  font-size: 20px;
  padding: 10px;
  outline: none;
  border: none;
  margin-right: 10px;
}

label{
  color: #fff;
}

app.js dosyamız ise henüz boş, onu da boş bir dosya olarak ekleyelim ve sayfamızı tarayıcıda açalım.


Bu yakışıklı sayfada perspektif ve döndürme eylemlerini simüle edip en son beğendiğimiz değerleri nasıl clipboarda kopyalayabileceğimizi göreceğiz. Sağdaki karenin ilgili özelliklerini soldaki kaydırma çubuklarının değerlerine göre değiştireceğiz. app.js dosyamızda değişkenleri tanımlayarak başlayalım.

 app6/app.js

Vue.createApp({
    data() {
        return {
            perspective: 100,
            rotateX: 0,
            rotateY: 0,
            rotateZ: 0
        }
    }
}).mount('#app')

Şimdi de görseldeki kaydırma çubuklarını v-model ile bu değişkenlere bağlayalım.

 app6/index.html 

        <div class="settings-container">
          <label>perspective: {{ perspective }}px;</label>
          <input type="range" min="0" max="999" v-model="perspective" />

          <label>rotateX: {{ rotateX }}deg; </label>
          <input type="range" min="-180" max="180" v-model="rotateX" />

          <label>rotateY: {{ rotateY }}deg; </label>
          <input type="range" min="-180" max="180" v-model="rotateY" />

          <label>rotateZ: {{ rotateZ }}deg; </label>
          <input type="range" min="-180" max="180" v-model="rotateZ" />

          <button type="button">Sıfırla</button>
          <button type="button">Kopyala</button>
        </div>

Kaydırma çubukları ve etiketleri bağladık. Birinci kısım bitti. Şimdi bu değerlere göre sağdaki karenin stilini değiştireceğiz. class adı box olduğuna göre :style değerine de box isimli bir hesaplanmış değer ile bağlama yapalım. 

 app6/index.html 

      <section class="output">
        <div class="box-container">
          <div class="box" :style="box"></div>
        </div>
      </section>

Şimdi box hesaplanmış değerini JavaScript dosyamızda tarif edelim. 

 app6/app.js

    },
    computed: {
        box() {
            return {
                transform: `
                    perspective(${this.perspective}px) 
                    rotateX(${this.rotateX}deg) 
                    rotateY(${this.rotateY}deg) 
                    rotateZ(${this.rotateZ}deg)
                `
            }
        }
    }
}).mount('#app')

Artık çalışıyor olmalı.


Geliştirici araçlarında da elemana baktığımızda dinamik olarak transform özelliğindeki değişimi gözleyebiliriz. 

Sıfırla butonuna geçelim, önce bir metod ekliyoruz.

 app6/app.js

    },
    methods: {
        sıfırla() {
            this.perspective = 100
            this.rotateX = 0
            this.rotateY = 0
            this.rotateZ = 0
        }
    }
}).mount('#app')

Buton tıkını da eklemeyi unutmayalım

 app6/index.html 

          <button type="button" @click.prevent="sıfırla">Sıfırla</button>

Clipboard'a kopya işi VueJs dışında JavaScript de gerektiriyor. Yeni bir başlık altında yapalım o işi.



Clipboard'a Kopyalama

Uygulamamızın son parçası görüntü istediğimiz gibi olduğunda stil değerlerini clipboard'a kopyalamak. Tarayıcıda bir elemanın içerdiği yazıyı kopyalamak için bir fonksiyon vardır. Önce butonumuza tıklanması için metod eklemesi yapalım. 

 app6/index.html 

          <button type="button" @click.prevent="sıfırla">Sıfırla</button>
          <button type="button" @click.prevent="kopyala">Kopyala</button>

Şimdi de Vue uygulamamız içinde methods nesnesi içinde kopyala() metodunu tanımlayalım. Kopyalama için JavaScript document.execCommand() metodunu kullanacağız. Bu metodun bir parametresi yapılacak işlem yani bizim amacımız için bu argüman 'copy' olacak. Bu metod seçili olan metni clipboard'a kopyalar. Ama bizim seçili bir yazı olarak stil değerini bir yerlere koymamız ve sonra seçmemiz lazım. Bunu da kod yazarak yapmamız lazım. Bir düşünelim. Sayfaya kod yoluyla bir textarea eklesek içine stil değerini yazdırsak sonra yazının tamamını seçtirsek ve sonra kopyalama metodunu çalıştırsak.

 app6/app.js

        kopyala() {
            const el = document.createElement("textarea")
            el.value = `transform: ${this.box.transform}`
            document.body.appendChild(el)
            el.select()
            document.execCommand("copy")
        }

el adını verdiğimiz bir textarea elemanı üretüyoruz. Yazısını box hesaplanmış değerinin transform özelliğinden aldığımız değerle CSS standardında üretiyoruz. Kopyalama metodunun çalışması için bu elemanın sayfada bulunması gerekiyor, sayfaya eklemek için appendChild() metodu kullanarak body içine ekliyoruz. Son olarak select() metodu ile yazdığımız yazının seçili olmasını sağlıyoruz. Bu noktada artık seçili bir yazı olduğunda göre kopyalama metodunu çağırabiliriz. Bir test edip görelim.


Kopyala butonuna her tıkladığımızda sayfaya resimdeki gibi bir textarea eklenir. İşlem gerçekleştiğini görmek istiyorsanız herhangi bir notepad falan açıp ctrl+v basarak ya da sağ tıklayıp yapıştır seçerek clipboard'a kopyalanan değeri görebilirsiniz. 

textarea elemanı sayfamızda görüntü bozuyor, bir de butona her tıklayınca bir tane daha sayfaya ekleniyor. Sayfada görünmesini engelleyip işimiz bitince de elemanı dökümandan tamamen çıkarmalıyız. 

        kopyala() {
            const el = document.createElement("textarea")

            el.setAttribute("readonly", "")
            el.style.position = "absolute"
            el.style.left = "-9999px"

            el.value = `transform: ${this.box.transform}`
            document.body.appendChild(el)
            el.select()
            document.execCommand("copy")

            document.body.removeChild(el)
        }

Elemanı readonly yapmamızın sebebi kazara tarayıcının falan tam o esnada bir şekilde elemana yazmasını engellemek. Sonra pozisyonunu absolute yapıp yatayda 9999 pixel sola gönderiyoruz. Eleman sayfada ama kullanıcı görüp erişemesin. Kopyalama işimiz bittikten sonra da removeChild() metodu ile dökümandan çıkarıyoruz. 

Bu makale çok uzadı ikiye bölsek iyi olacak. Gelecek bölümde buluşmak üzere, kalın sağlıcakla..



Hiç yorum yok:

Yorum Gönder