26 Nisan 2022 Salı

NwJs VueJs BulmaCSS JSONdata ile Masaüstü Rehber Uygulaması

 Merhaba, öyle döndüm, böyle döndüm VueJs ile bir masaüstü uygulaması geliştirdim. Bunu yaparken hem JavaScript hem de VueJs üzerine çok şey öğrendim. Şimdi bu öğrendiklerimi sizinle paylaşmak istiyorum. 

Öncelikle NwJs ile uygulama kalıbını oluşturarak başlayalım.


NwJs Yeni Masaüstü Uygulama

Öncelikle NwJs sitesinden en son halini indirelim. Ben ne olur ne olmaz deyip SDK sürümünü indirdim. İndirilen bir zip dosya, bunu herhangi bir yerde açalım.

Benim PC'de Portable klasörü var, yüklenmeden çalışan tüm programları oraya koyarım. Bendeki versiyon sdk-v0.63.1'miş bu arada.

NwJs uygulaması web sayfası şeklinde yaptığımız sayfayı masaüstü uygulama olarak çalıştırıyor. Extra bir şey öğrenmeden web programlama biliyorsanız masaüstü uygulama da yapabilirsiniz. Aslında NodeJs özelliklerinin tümünden yararlanabiliyormuş ama ben en basit halini kullanacağım. Eski adı da Node-Webkit imiş zaten. 

Öncelikle kafamda bir telefon rehberi uygulaması yapmak olduğu için teldef adında bir klasör tanımlayıp kodlarımı onun içinde yazmaya başladım. Kod editörü olarak be VSCode kullanıyorum, size de tavsiye ederim. teldef klasörü içinde sağ tıklayıp Code ile Aç seçince VSCode editöründe klasör açılıyor. Solda Explorer bölümünde klasörümüzdeki dosyalar görünecek, şimdilik klasör boş. 

Uygulamamızın görseli olan index.html dosyasını ekleyerek başlayalım. Explorer bölümüne sağ tıklayınca menü çıkacak, orada New File seçelim ve sonra da dosya adını index.html olarak girelim. 

Editörde index.html sayfası açılacaktır. Kursörü içine getirip ! işareti basarsak hemen bize standart bir html kodu önerecektir. Enter basarak onu kabul edelim. 

Sayfamız kodla doldu. Bu kod içinde <title> değerini değiştirelim sayfa içine de test için bir şeyler yazalım.

teldef/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Telefonlar</title>
</head>
<body>
    <h1>NwJs VueJs</h1>
</body>
</html>

Sayfamızı görmenin bir yolu gezginde üzerine çift tıklayarak dosya olarak açmak. Bunu yaptığımızda şunu görürüz,

Böyle yapınca kodda yaptığımız her değişiklikte sayfayı tarayıcıda yenilemek gerekiyor. Hem değişiklikler otomatik tarayıcıya bildirilsin hem de sayfamız bir serverdan yayınlanırken nasıl çalışıyor görmek için size bir VSCode eklentisi önereceğim. 

Eklentinin adı Live Server. Extensions sembolünü tıklayıp arama kutusuna live server yazarak kendisini bulup yükleyebiliriz. 

Tabi bende yüklü olduğu için install butonu görünmüyor. Live Server eklentisini yükledikten sonra editörde index.html dosyası içinde bir yere sağ tıklarsak menüye Open with Live Server seçeneği gelecektir. Bu seçeneği tıklatıp server'ı çalıştıralım. Server çalışacak ve tarayıcımız otomatik açılarak sayfamızı server'da bulacaktır. 

Live Server port 5500 kullanıyormuş onu da gördük. Şimdi bir değişiklik yapıp index.html dosyasını kaydedersek anında tarayıcı otomatik olarak sayfayı son hale update eder.

Sırada bunu nasıl NwJs ile masaüstü olarak göreceğiz? sorusu var. NwJs kullanmak için klasörümüzde adı package.json olan bir yapılandırma dosyası olması gerekiyor.  Explorer penceresinde teldef klasör içeriğine sağ tıklayıp, New File seçip isim olarak da package.json girelim dosyamızı editörde açalım. İçeriği şöyle olacak,

teldef/package.json

{
    "name": "telefon-rehberi",
    "main": "index.html",
    "window": {
        "resizable": false,
        "width": 300,
        "height": 600,
        "toolbar": true
    }
}

Genişliği 300 piksel, yükseklik 600. Boyut değiştirmesini de kapattım, uygulama penceresi böyle uzun dikine bir sayfa. Tabi bu tarayıcıda bakarsak etkili değil, bunlar masaüstü uygulama özellikleri.

Hazırız, masaüstü uygulama olarak çalıştırmak için klasörümüzde bir terminal açıp

nw.exe .

yazmamız gerekiyor. Ancak sistemde nw.exe dosyasının yeri bilinmediği için klasöre bir kısayol ekleyelim. Gezginde klasör içinde boş bir yere sağ tıklayıp Yeni -> Kısayol seçelim. Gözat butonuna tıklayarak nw.exe dosyasını bulalım ve Tamam butonuna tıklayalım. Sonraki butonuna tıkladığımızda kısayol için bir ad sorar buraya da Run yazalım. Son butonuna tıklayarak işlemi bitirelim. 

Şimdi Run kısayoluna çift tıklarsak index.html sayfamız açılmaz. Parametresinde nokta yazmadığımız için boş bir uygulama açılır.

Run kısayoluna gezginde sağ tıklayıp özelliklerini seçelim. Hedef özelliğinde en sona çift tırnak içinde "." yazalım ve Başlama yeri değerini de boşaltalım ki bu klasörde başlasın ve kaydedelim.

Okey artık Run kısayoluna çift tıklayınca uygulamamız açılacaktır.

Uygulamamız çalışmaya başladı. Bunun bir masaüstü uygulaması olmasını istediğim için tüm gereken JavaScript ve CSS kütüphane dosyalarını internetten indirip klasörümüze koyacağım. 



VueJs BulmaCSS ve LodashJs Dosyaları

BulmaCSS uygulamamızda kullanacağımız stil kütüphanesi. bulma.min.css dosyasını indirip teldef klasörüne kaydedelim. Sonra da index.html dosyası <head> kısmında dosyayı sayfaya çağıralım. 

teldef/index.html

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Telefonlar</title>
    <link rel="stylesheet" href="bulma.min.css">
</head>

Dosyayı kaydedelim tarayıcıda açıksa görsel stilinin değiştiğini fark edeceksiniz. Masaüstü uygulamayı da kapatmadan sayfada sağ tıklayıp Uygulamayı yeniden yükle seçeneğini tıklarsak tekrar yüklenecektir. Anlık görsel sonuçları görebilmek için ben tarayıcıyı hep açık tutuyorum, ancak ilerde göreceğimiz gibi masaüstü ve web uygulaması arasında fark olabiliyor. 

VueJs için de vue.global.js dosyasını klasörümüze indirelim. Sonra da index.html dosyamız head kısmında çağıralım.

teldef/index.html

    <title>Telefonlar</title>
    <link rel="stylesheet" href="bulma.min.css">
    <script src="vue.global.js"></script>
</head>

Bunun çalıştığını kod yazdıkça göreceğiz. Şimdilik Geliştirici Araçları konsolda hata görmüyorsak sıkıntı yok demektir. Sadece Vue geliştirici dosyasını kullandığımızı belirten bir uyarı gelir, o kadar. Bu arada masaüstü uygulamada da Geliştirici araçları penceresi açabiliyoruz. Uygulama penceresinde sağ tıklayıp incele ya da Arkaplan sayfasını incele seçersek DevTools penceresi açılır. İkincisi bizi direk konsola götürür. 

LodashJs nesneler, array'ler ve stringlerle ilgili çok yardımcı metodlar bulunduruyor. Ben bu JavaScript verileri içinde çok kaybolduğumdan işimi çok kolaylaştırdı bu kütüphane. lodash.min.js dosyasını da klasörümüze indirelim. Sonra da sayfaya dahil edelim. 

teldef/index.html

    <title>Telefonlar</title>
    <link rel="stylesheet" href="bulma.min.css">
    <script src="vue.global.js"></script>
    <script src="lodash.min.js"></script>
</head>

İnecekler indi binecekler bindi, artık gidebiliriz. 

JavaScript kodumuz için klasöre app.js adında bir dosya ekleyelim ve sayfa yüklenmesi sonunda bu dosyayı da sayfaya dahil edelim. 

teldef/index.html

<body>
    <h1>NwJs VueJs :)</h1>

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

Vue uygulaması yazmaya başlayabiliriz artık. Öncesinde yaptığım bir şeyi daha söyleyeyim. Masaüstü uygulama ve tarayıcıdaki aynı görünmesi için aynı piksel genişlikte görsel olması gerekiyor. Yoksa CSS kütüphaneleri genişliğe göre görsel yerleşimini çok değiştiriyorlar. Aynı genişliğe gelsinler diye tarayıcıda Geliştiici araçları penceresini açtım onun genişliğini tutup çekeleyerek görsel genişliğini 300 piksele kısıtladım. 





VueJs Eklemesi

Çok dinamik sayfamız olacak o yüzden VueJs olmasa hayatta beceremezdik. Tersden başlayalım. Önce app.js dosyasında yeni bir Vue nesnesi oluşturalım.

teldef/app.js

const app = Vue.createApp({
    data() {
        return {
            test: "Merhaba Vue!"
        }
    }
}).mount("#app")

Şimdilik test adında bir değişken tanımladık. Şimdi de görsele monte edileceği #app elemanı tanımlayalım.

teldef/index.html

<body>
    <div id="app">
        <h2>{{ test }}</h2>
    </div>

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

VSCode editörde id değeri app olan bir div için div#app enter basarsanız kendi otomatik yazar, ama küçük-büyük işareti koynadan yazın. Mesaj gelmiş olmalı,

VueJs de çalıştı. Görseli hazırlamaya başlayabiliriz.



Görsel Tasarım

Cep telefonumdaki gibi olsun istedim sayfayı dikey olarak ikiye böleceğim solda büyük alanda rehber bilgisi olacak, sağda da ince bir sütun halinde harfleri koyup tıklanınca o harften başlayan isimleri göstereceğiz. Öncelikle örnek bir rehber verisini JSON data olarak tanımlayalım.

teldef/app.js

const app = Vue.createApp({
    data() {
        return {
            rehber: [
                {
                    "isim": "A Kişi",
                    "emailler": {"özel": "akisi@gmail.com",
                        "iş": "akisi@turkpoem.com"},
                    "telefonlar": {"tel1": "05xx xxx xx x1",
                        "tel2": "05xx xxx xx x2"},
                    "şirket": "Poem Mühendislik"
                },
                {
                    "isim": "D Kişi",
                    "emailler": {"özel": "dkisi@windowslive.com",
                        "iş": "dkisi@company.com"},
                    "telefonlar": {"tel1": "05xx xxx xx d1"},
                    "şirket": "company"
                }
            ]
        }
    }
}).mount("#app")

Görselde iki bölüm olacak demiştik onları da bir yerleştirelim.

teldef/index.html

    <div id="app">
        <div class="buttons">
            <!-- SOL DIV BAŞI -->
            <div style="width: 85%; height: 550px;">

            </div><!-- SOL DIV SONU -->

            <!-- SAĞ DIV -->
            <div class="has-background-white-bis" style="width: 10%;">

            </div><!-- SAĞ DIV SONU -->
        </div>
    </div>

buttons class değeri içindeki butonları yan yana yapıştırıyor, baktım div'leri de yapıştırıyor , onu kullandım. Sağ div daha kolay ama tüm karakterler elimizde olsun diye 2 değişken daha tanımlayalım.

teldef/app.js

const app = Vue.createApp({
    data() {
        return {
            rehber: [
....
            ],
            küçükHarfler: ["a","b","c","ç","d","e","f","g","ğ","h","ı",
                "i","j","k","l","m","n","o","ö","p","q","r","s","ş","t",
                "u","ü","v","w","x","y","z"],
            büyükHarfler: ["A","B","C","Ç","D","E","F","G","Ğ","H","I",
                "İ","J","K","L","M","N","O","Ö","P","Q","R","S","Ş","T",
                "U","Ü","V","W","X","Y","Z"]
        }
    }
}).mount("#app")

Görselin sağ tarafına harfleri sıralayalım.

teldef/index.html

            <!-- SAĞ DIV -->
            <div class="has-background-white-bis pl-2" style="width: 10%;">
                <div v-for="harf in büyükHarfler" class="px-1"
style="line-height: 16px;">
                    <a href="" class="has-text-grey-light"
                        style="font-size: 12px;" @click.prevent="">{{harf}}</a>
                </div>
            </div><!-- SAĞ DIV SONU -->

class değerine pl-2 ekledim, çünkü yazılar sola yapışık oldu pl : padding-left anlamında bir class değeri. Linkler sayfada yenileme falan yapmasın diye @click.prevent ile default aksiyonlarını kapattım ama şimdilik metod ismi girmedik, sırası gelince yaparız. Görselde sağ tarafa harfler geldi.

Sol tarafa da rehberden isimleri koyarak başlayalım,

teldef/index.html

            <!-- SOL DIV BAŞI -->
            <div style="width: 85%; height: 550px;">
                <div v-for="kayıt in rehber">
                    <a href="" @click.prevent="">{{ kayıt.isim }}</a>
                </div>
            </div><!-- SOL DIV SONU -->

Sadece isimler sıralı geldi. Bu arada bir şey fark ettim, bu rehberi harf sırasında tutmak için her kayıt değişiminde uğraşacağız. En iyisi rehber kayıtlar karışık olsun biz sıralı bir liste elde edip yayınlayalım. Bu amaçla bir hesaplanmış değer üretelim onu kullanalım. Vue nesnemiz computed bölümünü ekleyelim.

teldef/app.js

const app = Vue.createApp({
    data() {
....
    },
    computed: {
        isimeRehber() {
            return _.orderBy(this.rehber,(el)=>{
                return el.isim.toUpperCase()
            },["asc"])
        }
    }
}).mount("#app")

_.orderBy() metodu bize LodashJs tarafından sunulan bir güzellik. İnşallah Türkçe'ye de uygun sıralama yapar. Şimdi sayfada listemizi isimeRehber adındaki bu hesaplanmış değer üzerinden gösterelim. 

teldef/index.html

            <!-- SOL DIV BAŞI -->
            <div style="width: 85%; height: 550px;">
                <div v-for="kayıt in isimeRehber">
                    <a href="" @click.prevent="">{{ kayıt.isim }}</a>
                </div>
            </div><!-- SOL DIV SONU -->

Sıkıntı yok, hala isimler geliyor. Denemesini sonra yaparız. Biraz görsele odaklanalım. Hangi isime tıklanırsa onun altında ayrıntıları göstermek istiyoruz. seçiliNr adında bir değişken olsun eğer  kayıt'ın index değeri buna eşitse ayrıntısını gösterelim. İsime tıklanınca da o isme ait kayıt'ın index'ini seçiliNr yapalım. Önce değişken tanımı,

teldef/app.js

            büyükHarfler: ["A","B","C","Ç","D","E","F","G","Ğ","H","I",
                "İ","J","K","L","M","N","O","Ö","P","Q","R","S","Ş","T",
                "U","Ü","V","W","X","Y","Z"],
            seçiliNr: 0

Şimdi görsele ayrıntı için bir div ekleyelim.

teldef/index.html

           <div v-for="(kayıt, index) in isimeRehber">
               <a href="" @click.prevent="seçiliNr = index">{{ kayıt.isim }}</a>
              <div v-if="index === seçiliNr" class="has-background-light">
                  ayrıntı</div>
           </div>

v-for direktifini (kayıt, index) şeklinde işleyerek index değerini de aldık kayıtlardan.

Hangisine tıklarsak ayrıntı orada ortaya çıkıyor. Bu arada her yüklemede DevTools penceresini açmaktan sıkıldım. Koda ekleme yapalım uygulama yüklenince açık gelsin.

teldef/app.js

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

nw.Window.get().on("loaded", ()=>{
    nw.Window.get().showDevTools()
})

Artık Uygulamayı yeniden yükle seçince DevTools penceresi de açık gelecek. İşimiz bitince sileriz. Ayrıntı görseline telefonları ekleyerek devam edelim.

teldef/index.html

          <div v-if="index === seçiliNr" class="has-background-light">
              <h6 class="has-background-dark has-text-white p-1">Telefonlar :</h6>
              <p v-for="(tel, key) in kayıt.telefonlar"
                  class="has-background-danger-light column py-1">
                  <span>{{key}} :</span> {{ tel }}
              </p>
          </div>

Telefonlar görsele geldi

Emailleri de görsele getirelim,

teldef/index.html

<div v-if="index === seçiliNr" class="has-background-light">
<h6 class="has-background-dark has-text-white p-1">Telefonlar :</h6>
<p v-for="(tel, key) in kayıt.telefonlar"
class="has-background-danger-light column py-1">
<span>{{key}} :</span> {{ tel }}
</p>
<h6 class="has-background-dark has-text-white p-1">Emailler :</h6>
<a v-for="(email, key) in kayıt.emailler" :href="'mailto:' + email"
class="has-background-primary-light column py-1">
<span>{{key}} :</span> {{email}}
</a>
</div>

sonuç,

Şirket bilgisi de koyalım,

teldef/index.html

<h6 class="has-background-dark has-text-white p-1">Şirket :</h6>
<p class="pl-2">{{ kayıt.şirket }}</p>
</div>

sonuç

Sağdaki harf sütununda o harfle başlayan isim varsa harfin rengini belirgin hale getirelim. Her harf için true-false değerler dönen bir metod tanımlayalım o harfle başlayan kayıt varsa bir class değeri verelim.

teldef/app.js

    computed: {
        isimeRehber() {
            return _.orderBy(this.rehber,(el)=>{
                return el.isim.toUpperCase()
            },["asc"])
        }
    },
    methods: {
        isimVar(harf) {
            let result = false
            this.isimeRehber.forEach(v => {
                if (v.isim.toUpperCase().startsWith(harf)) {
                    result = true
                }
            })
            return result
        }
    }
}).mount("#app")

isimeRehber hesaplanmış değeri içinde isim değeri verilen harfin büyüğü ya da küçüğü ile başlayan kayıt varsa true değeri dönüyoruz. Görselde harfleri dizerken renklendirmeyi yapalım. 

teldef/index.html

            <!-- SAĞ DIV -->
            <div class="has-background-white-bis pl-2" style="width: 10%;">
                <div v-for="harf in büyükHarfler" class="px-1"
                        style="line-height: 16px;">
                    <a href="" :class="{ 'has-text-grey-light': !isimVar(harf),
                        'has-text-weight-bold': isimVar(harf) }"
                        style="font-size: 12px;" @click.prevent="">{{harf}}</a>
                </div>
            </div><!-- SAĞ DIV SONU -->

İsim yoksa yazı rengi gri olacak , varsa default mavi renk olacak ama belirgin olsun diye bir de koyu olacak.

A ve D harfleri ile başlayan kayıt bulunduğu için renkleri belirgin oldu. Sırada bir harfe tıklanınca sıralamada o harfle başlayan ilk kayıt'a gitmek var. harfeGeç() adında bir metod tanımlayalım. 

teldef/app.js

    methods: {
        harfeGeç(harf) {
            if (this.isimVar(harf)) {
                let result = _.findIndex(this.isimeRehber, function(obj) {
                    return obj.isim.toUpperCase().startsWith(harf)
                })
 
                this.seçiliNr = result
            }
        },

Harflerden biri tıklanınca bu metod çağrılsın. Eğer o harfle başlayan kayıt varsa , ilk kaydın index'i seçiliNr ile eşitler,

teldef/index.html

                <div v-for="harf in büyükHarfler" class="px-1"
                        style="line-height: 16px;">
                    <a href="" :class="{ 'has-text-grey-light': !isimVar(harf),
                        'has-text-weight-bold': isimVar(harf) }"
                        style="font-size: 12px;"
                        @click.prevent="harfeGeç(harf)">{{harf}}</a>
                </div>

Bu JavaScript baya bir Türkçe karakter biliyor. Burada kesin takılırız demiştim, olmadı, çalışıyor. Denemek için rehbere bir kayıt daha ekleyelim bakalım hem sıralama nasıl olacak, hem tıklanınca ilkine gidecek mi?

teldef/app.js

            rehber: [
                {
                    ....
                },
                {
                    ....
                },
                {
                    "isim": "a aKişi2",
                    "emailler": {"özel": "akisi2@windowslive.com",
                        "iş": "akisi2@company.com"},
                    "telefonlar": {"tel1": "05xx xxx xx a2"},
                    "şirket": "company a2"
                }
            ],

Mesela A Kişi seçiliyken sağda A harfine tıklanırsa üstteki a aKişi2 ye geçiyor, güzel. 

Kayıtları diskte bir JSON dosyasına kaydetmek istersek, işte bu tarayıcıda çok zor çalışır. Ancak NodeJs yardımıyla sayfayı yayınlarsak özellikler kullanarak falan yapılabiliyormuş da ben nasıl olur bilmiyorum. Tarayıcılar server'daki dosyalara normal şartlar altında erişemiyorlar. Ama bizim masaüstü uygulamamız hard diskteki dosyalara erişebilir. 

Dosyadan verileri okumak için ilk önce bir veri dosyası oluşturup içine deneme verileri koyalım. Dosyamızın adı data.json olsun,

teldef/data.json

[
    {
        "isim": "A Kişi",
        "emailler": {"özel": "akisi@gmail.com",
            "iş": "akisi@turkpoem.com"},
        "telefonlar": {"tel1": "05xx xxx xx x1",
            "tel2": "05xx xxx xx x2"},
        "şirket": "Poem Mühendislik"
    },
    {
        "isim": "D Kişi",
        "emailler": {"özel": "dkisi@windowslive.com",
            "iş": "dkisi@company.com"},
        "telefonlar": {"tel1": "05xx xxx xx d1"},
        "şirket": "company d"
    },
    {
        "isim": "B Kişi",
        "emailler": {"özel": "bkisi@hotmail.com",
            "iş": "bkisi@bcompany.com"},
        "telefonlar": {"tel1": "05xx xxx xx b1"},
        "şirket": "company b"
    }
]

Böyle düzenli göründüğüne bakmayın, üzerine veri yazılınca hepsi tek satır olacak. Şimdi uygulama yüklenince bu veriyi okumak için loadData() metodunu tanımlayalım.

teldef/app.js

    methods: {
        loadData() {
            const fs = nw.require('fs')

            fs.readFile("data.json", (err, txt) => {
                if (err) {
                    console.error(err)
                    return
                }
                this.rehber = JSON.parse(txt)
            })
        },
....

NwJs metodları yardımıyla dosya içeriğini okuyoruz, eğer hata olursa konsola hata mesajı yazılıyor. Okunan string değer JSON veriye çevrilip rehber'e saklanıyor. Bu metodu çağırmak için VueJs olaylarından mounted() metodunu kullanmaya çalıştım ama başarılı olmadı sanırım NwJs uygulamayı açarken bu Vue olay rutinleri etkisiz kaldı. Öyleyse NwJs olayına bağlayayım dedim. Hatırlarsak DevTools penceresi açılsın diye uygulamanın yüklendiği loaded olayına bir fonksiyon yazmıştık. Onun içine koydum, çalıştı. 

teldef/app.js

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

nw.Window.get().on("loaded", ()=>{
    app.loadData()
    nw.Window.get().showDevTools()
})

Dosyadaki veriler sıralanmış halde geldi. loadData() metodu tarayıcıda çalışmayacaktır, daha require satırında çakılır, çünkü nw kütüphanesi tanımlı değil. Zaten tarayıcıyı sadece görsel kodunu yazarken çabuk cevap veriyor diye kullanıyordum, bundan sonra masaüstü uygulamada devam ederiz. Sıra geldi kayıt düzenleme ya da yeni kayıt eklemeye. 




Kayıt Düzenleme ve Yeni Kayıt Ekleme

Tüm CRUD (create, read, update, delete) uygulamalarda kayıt düzenlemek ve yeni kayıt eklemek için aynı form kullanılır. Sayfamızın en altında bir form oluşturacağız. Bu form normalde görünmeyecek, ne zaman bir kaydı düzenleme butonuna basarsak ya da yeni kayıt ekleme butonuna basarsak, bu form görünür olacak, kayıt işlemi bitince tekrar yok olacak. 

formGöster adında bir değişken true-false değeriyle formun görünürlüğünü kontrol etsin.

teldef/app.js

....
            seçiliNr: 0,
            formGöster: false
        }
....

Görsele de bir div elemanı ekleyelim bu değişkene göre görünsün.

teldef/index.html

    <div id="app">
....

        <!-- FORM -->
        <div class="has-background-grey-light" v-show="formGöster">
            FORM
        </div><!-- FORM SONU -->
    </div>

Formun görünmesi için DevTools penceresi konsolda 

app.formGöster = true

yazarak test edebiliriz. Div elemanı en alta gelmiş olmalı,

Form görselini hazırlamaya başlayacağımız için formGöster değerini default true yapalım ki her seferinde DevTools'a yazmaya uğraşmayalım

teldef/app.js

....
            seçiliNr: 0,
            formGöster: true
        }
....

Veri tabanına dayalı uygulamalarda formlarda kullanmak üzere bir sahte (geçici) kayıt olur, düzenlenecek kayıt buraya konduğunda forma gelir. düzenlenenKayıt adıyla böyle bir değişken ekleyelim. 

....
            formGöster: true,
            düzenlenenKayıt: {
                    "isim": "İsim",
                    "telefonlar": {"tel1": "T1", "tel2": "T2"},
                    "emailler": {"özel": "o@c", "iş": "i@p"},
                    "şirket": "company"
            }
....

Görsele formu eklemeye başlayalım. 

teldef/index.html

        <!-- FORM -->
        <div class="has-background-grey-light" v-show="formGöster">
            <label class="pl-2 has-text-weight-bold has-text-black">İsim :</label>
            <input type="text" class="input" v-model="düzenlenenKayıt.isim">
        </div><!-- FORM SONU -->

İsmi düzenlemek için input elemanı düzenlenenKayıt.isim değerine v-model direktifi ile bağlı. Görsele bir başlık ve bir input elemanı ekledik,

Şirket işi de kolay onu da ekleyelim.

teldef/index.html

        <!-- FORM -->
        <div class="has-background-grey-light" v-show="formGöster">
            <label class="pl-2 has-text-weight-bold has-text-black">İsim :</label>
            <input type="text" class="input" v-model="düzenlenenKayıt.isim">

            <label class="pl-2 has-text-weight-bold has-text-black">Şirket :</label>
            <input type="text" class="input" v-model="düzenlenenKayıt.şirket">
        </div><!-- FORM SONU -->

Telefon numaraları ve emailler işi çetrefilli olacak. Çünkü orada sadece value değil key değerlerinin de değiştirilebilir , eklenebilir ve çıkarılabilir olması gerekiyor. Önce telefon için değerleri göstererek başlayalım.

teldef/index.html

        <!-- FORM -->
        <div class="has-background-grey-light" v-show="formGöster">
            ....

            <label class="pl-2 has-text-weight-bold has-text-black">
                Telefonlar : </label>
            <div class="control mb-0"
                    v-for="(telNo, key) in düzenlenenKayıt.telefonlar">
                <input type="text" class="input" :value="key" style="width: 30%;"
                    @change.prevent="">
                <input type="tel" class="input" placeholder="Numara Giriniz"
                    v-model="düzenlenenKayıt.telefonlar[key]" style="width: 65%;">
            </div>

            <label class="pl-2 has-text-weight-bold has-text-black">Şirket :</label>
            <input type="text" class="input" v-model="düzenlenenKayıt.şirket">
        </div><!-- FORM SONU -->

Şirket bilgisi üzerine koyduk. İki önemli nokta var. İlki key değerini direk v-model ile bağlayamayız, JavaScript nesnesinde key değiştirirken o iş için bir metod yazmamız gerekiyor. O yüzden key değerini sadece göstermek için :value ile bağladık. Input değeri change olayına da (yani enter basılıp ya da kursör kutu dışına tıklanması durumu) bir metod çağıracağız.

İkinci input için de 

v-model="telNo"

yazınca telefon numarasını değiştiremediğini daha sonra kayıt yaparken fark ettim. Denemek ve görmek isterseniz telNo değerine bağlayın, çalıştırın, numarayı değiştirip, konsoldan 

app.düzenlenenKayıt.telefonlar

yazarak kontrol edin. Kayıttaki numara değişmeyecektir. O yüzden direk olarak düzenlenenKayıt.telefonlar içinde alakalı yerine bağladım. 

Emailler de buna benziyor onu da yazalım , sonrasında şu key değiştirme metodlarını yerine koyarız. 

teldef/index.html

....
            <label class="pl-2 has-text-weight-bold has-text-black">
                Emailler : </label>
            <div class="control mb-0"
                    v-for="(email, key) in düzenlenenKayıt.emailler">
                <input type="text" class="input" :value="key" style="width: 30%;"
                    @change.prevent="">
                <input type="email" class="input" placeholder="Email Giriniz"
                    v-model="düzenlenenKayıt.emailler[key]" style="width: 65%;">
            </div>

            <label class="pl-2 has-text-weight-bold has-text-black">Şirket :</label>
            <input type="text" class="input" v-model="düzenlenenKayıt.şirket">
        </div><!-- FORM SONU -->

Form görseli hazır gibi 

En alta da 2 button ekleyelim, biri iptal diğeri kayıt etmek için.

teldef/index.html

....
            <label class="pl-2 has-text-weight-bold has-text-black">Şirket :</label>
            <input type="text" class="input" v-model="düzenlenenKayıt.şirket">

            <div class="buttons">
                <button class="mx-1 button is-danger is-small has-text-weight-bold"
                    @click.prevent="formGöster = false" ref="iptal">İptal</button>
                <button class="mx-1 button is-primary is-small has-text-weight-bold
                    has-background-primary-dark" @click="">Kaydet</button>
            </div>
        </div><!-- FORM SONU -->

İptal butonu kodu kolay olduğu için hemen yazdım. formGöster değeri false yapılınca hiç bir şey yapmadan form görünmez olacaktır. Son görünüm şöyle oldu

Şimdi şu key değiştirme rutinlerini yazalım. Telefon key'lerinden başlayalım.

teldef/index.html

            <div class="control mb-0"
                    v-for="(telNo, key) in düzenlenenKayıt.telefonlar">
                <input type="text" class="input" :value="key" style="width: 30%;"
                    @change.prevent="chTelKey($event,key)">
                <input type="tel" class="input" placeholder="Numara Giriniz"
                    v-model="düzenlenenKayıt.telefonlar[key]" style="width: 65%;">
            </div>

$event olay bilgisinden yeni key değerini elde edeceğiz, key parametresinde ise eski key değeri var. Metodu tanımlayalım,

teldef/app.js

    methods: {
        chTelKey(ev, oldValue) {
            this.düzenlenenKayıt.telefonlar[ev.target.value] =
                    this.düzenlenenKayıt.telefonlar[oldValue]
            delete this.düzenlenenKayıt.telefonlar[oldValue]
        },
....

İlk satır yeni key içeriğine eski key içeriğini alıyor, bu aynı zamanda yeni key eklemiş oluyor. İkinci satır da eski key'e ait elemanı siliyor. Denemek için formda bir telefon key değiştirmeden önce ve sonrasında düzenlenenKayıt değişkenine konsolda bakarsak,

Benzer işi emailler için yapmaya chEmailKey() metodu kullanalım.

teldef/index.html

            <div class="control mb-0"
                    v-for="(email, key) in düzenlenenKayıt.emailler">
                <input type="text" class="input" :value="key" style="width: 30%;"
                    @change.prevent="chEmailKey($event,key)">
                <input type="email" class="input" placeholder="Email Giriniz"
                    v-model="düzenlenenKayıt.emailler[key]" style="width: 65%;">
            </div>

teldef/app.js

    methods: {
        chEmailKey(ev, oldValue) {
            this.düzenlenenKayıt.emailler[ev.target.value] =
                    this.düzenlenenKayıt.emailler[oldValue]
            delete this.düzenlenenKayıt.emailler[oldValue]
        },

Yukarıda ayrıntıların en altına Düzenle ve Sil adında 2 button ekleyelim. 

teldef/index.html

....
<p class="pl-2">{{ kayıt.şirket }}</p>

<button class="button is-primary is-small has-background-primary-dark"
    @click="edit(index)">Düzenle</button>
<button class="button is-danger is-small" @click="sil(index)">Sil</button>
<h6></h6>
</div>

edit(index) metodu formu görünür yapacak ve forma tıklandığı kaydın değerlerini kopyalayacak. sil(index) metodu ise rehberden o kaydı silecek. Sil butonu kodunda rehberi hard diske kayıt işi de olacak, şimdilik sonraya bırakalım. Görselimiz şöyle oldu,

edit(index) metodu tanımını yapalım.

teldef/app.js

    methods: {
        edit(index) {
            this.formGöster = true
            this.editIndex = index
            setTimeout(()=>{
                window.scrollTo(0,1000)
            }, 1000)
            if (index < 0) {
                this.düzenlenenKayıt = {
                    "isim": "A",
                    "telefonlar": {"tel1": "T1"},
                    "emailler": {"iş": "i@p"},
                    "şirket": "company"
                }
                this.seçiliNr = -1
            } else {
                this.düzenlenenKayıt =
                    JSON.parse(JSON.stringify(this.isimeRehber[index]))
            }
        },

formGöster değerini true yaparak formun görünür olmasını sağlıyor. Bu arada formGöster başlangıç değerini 

            formGöster: false,

yapalım da başlangıçta form görünmez olsun. editIndex değişkeni daha sonra formda Kaydet butonuna bastığımızda kaydın hangi index'teki kayıt üzerine yazılacağı bilgisini saklayacak. Bunu da Vue nesnemiz data() bölümüne ekleyelim. 

    data() {
        return {
            editIndex: 0,
....

Sonra bir setTimeout() ekledim, aşağı form görüntüsüne scroll etsin diye. Hemen scroll edince daha VueJs devreye girip formu göstermemiş oluyor, bu yüzden biraz gecikmeli kaydırdım aşağıya. 

Sonra index değeri sıfırdan küçük mü? diye bakıyoruz. Bunun şimdi bir anlamı yok, çünkü index illa ki 0 ya da büyük olur. Ama ileride yeni kayıt eklemek için bir buton koyacağız, o buton aynı metodu index değeri -1 olacak şekilde çağırsın, nasıl olsa aynı form kullanılıyor, ayrı ayrı metod yazmayalım. 

index değeri sıfırdan küçükse yeni kayıtdır , dummy değerler vererek formu dolduruyoruz. seçiliNr değerini de -1 yapıyoruz. Normalde Düzenle butonuna tıklandığına göre bu değer düzenlenen kaydın index değeri ile aynı. seçiliNr değeri -1 olunca yukarıda listede hiç bir kaydın ayrıntısı görünmeyecektir.

Eğer index değeri sıfır ya da büyük bir değerse, bu mevcut bir kayıt düzenleniyor demektir. Bu durumda form bilgileri düzenlenen kayıttaki bilgilerle aynı olmalı. Peki neden 

this.düzenlenenKayıt = this.isimeRehber[index]

yazmadık ta böyle önce JSON'dan String'e , sonra tekrar String'den JSON'a çevirdik. Direk eşitleme yapsaydık JavaScript için ikisi aynı nesne olacaktı ve formda kullanıcı değer değiştirdikçe orjinal kayıt da değişecekti. Halbuki iptal edilme olasılığı var. Ancak Kaydet butonu basılırsa formda yapılan değişiklikler kaydedilmeli. Ayrıca isimeRehber bir hesaplanmış değer (computed) , biz kaydı ona değil rehber değişkenine yapacağız. Bu JSON-String-JSON dönüşümü ile nesnenin değil değerlerinin kopyalandığı yeni bir nesne üretiyoruz.

Herhangi bir kayıt için Düzenle butonuna basarak forma o kaydın bilgileri geldiğini test edelim.

Hadi elimiz değmişken sayfanın en üstüne yeni kişi eklemek için bir buton koyalım.

teldef/index.html

    <div id="app">
        <div class="buttons">
            <!-- SOL DIV BAŞI -->
            <div style="width: 85%; height: 550px;">
                <button class="button is-link is-small" @click="edit(-1)">
                    Yeni Kişi</button>
                <div v-for="(kayıt, index) in isimeRehber">
....

Görsele yeni bir buton geldi aynı formu index değeri -1 olarak açtırıyor.

Tıklarsak formda dummy değerler ile açıldığını görürüz. Bu arada formda üzerinde Kaydet yazan butonun aslında yeni kayıt olunca Ekle yazması gerekmez mi? Bunu da kaydetTxt adında bir hesaplanmış değer yardımıyla yapalım.

teldef/app.js

    computed: {
        kaydetTxt() {
            if (this.seçiliNr >= 0) {
                return "Kaydet"
            } else {
                return "Ekle"
            }
        },

teldef/index.html

                <button class="mx-1 button is-primary is-small has-text-weight-bold
                    has-background-primary-dark" @click="">{{ kaydetTxt }}</button>
            </div>
        </div><!-- FORM SONU -->

Her yeri süsledik bir Kaydet meselesine gelemedik, bir de Sil meselesi. İkisinin ortak yanı, bu butonlar tıklanınca işlerinin en sonunda rehber değişkenini hard diske kaydetmeleri gerekiyor. Öncelikle saveData() adını verdiğimiz metodu tanımlayalım. 

teldef/app.js

    methods: {
        saveData() {
            const fs = nw.require('fs')

            fs.writeFile("data.json", JSON.stringify(this.rehber), function(err) {
                if (err) {
                    console.error(err)
                    return
                }
            })
        },

Yine sadece masaüstü uygulamaya özel komutlar var. Kod hiç bir şey yapmıyor, sadece rehber değişkenini data.json dosyasına string olarak kaydediyor. Hata olursa da konsola yazıyor. 

Formdan önce yukarıda listede ayrıntıdaki Sil butonunun kodunu yazalım. Biliyorsunuz sil(index) metodunu çağırıyordu bu buton. 

teldef/app.js

    methods: {
        sil(index) {
            let rehberIndex = this.rehber.findIndex(v =>
                    v === this.isimeRehber[index])
            let v = this.rehber[rehberIndex]
            let text = v.isim + " silinecek, emin misin?"
            if (confirm(text) == true) {
                console.log(this.rehber.splice(rehberIndex,1))
                this.saveData()
            }
        },

Denemek isterseniz this.saveData() satırını comment out yapın kayıt hard diskte silinmesin uygulama tekrar başlatılınca geri gelsin. Zaten 3 tane kaydımız var şurada, ondan da olmayalım. 

Napıyoruz? İlk önce isimeRehber listesinde index'ini bildiğimiz bu kaydın rehber değişkeni içindeki index'ini bulup rehberIndex adında geçici bir değişkene koyuyoruz. Bu findIndex() metodunu anlamam biraz zaman aldı ama anladım. Parametresinde verilen fonksiyon ile sırayla tüm array değerlerini test ediyor ve true değer dönerse aha index bu diyor. Burada da rehber değişkeni içindeki tüm objeleri tek tek v değerine alıyor v değeri isimeRehber değişkeni içindeki seçili obje ile aynıysa fonksiyon true değer dönüyor ve o andaki v objesinin index'i rehberIndex değerine konuyor. 

Sonra "... silinecek emin misin? " diye kullanıcıdan onay isteyen bir konfirmasyon popup açılıyor (insan biraz kibar sorar). confirm(text) komutu popup'ı açar ve açılan popup'ta Tamam butonu tıklanırsa true değer döner. 

Kullanıcı kaydın silinmesine onay verdiyse splice() metodu yardımı ile o obje rehber array'inden siliniyor. Sonra da en son rehber saveData() ile hard diske kaydediliyor. Burada silinen değeri konsola yazdırdım, ama siz isterseniz yazdırmadan o satırda direk

                this.rehber.splice(rehberIndex,1)

yazabilirsiniz. 

Sırada Kaydet butonu var. Ona da editSave() adında bir metod yazalım.

teldef/index.html

                <button class="mx-1 button is-primary is-small has-text-weight-bold
                    has-background-primary-dark"
                    @click="editSave">{{ kaydetTxt }}</button>

teldef/app.js

    methods: {
        editSave() {
            if (this.editIndex < 0) {
                this.rehber.push(this.düzenlenenKayıt)
            } else {
                let rehberIndex = this.rehber.findIndex(v =>
                    v === this.isimeRehber[this.editIndex])
                this.rehber[rehberIndex] =
                    JSON.parse(JSON.stringify(this.düzenlenenKayıt))
            }
            this.saveData()
            this.formGöster = false
            window.scrollTo(0,0)
        },

Aslında tüm web yazılımcılar forma görünmeyen bir input eklerler ve düzenlenen kaydın index değerini orada saklarlar. Ama ben Vue kullanıyorum editIndex değişkeninde bu değeri saklayabilirim. 

editIndex değeri sıfırdan küçükse yeni kayıt ekleniyordur bu durumda eklemeyi direk rehber değişkenine push() metodu ile ekleriz. Yok eğer mevcut kayıt düzenleniyorsa yine rehberdeki index karşılığını bulup bu sefer düzenlenenKayıt içindeki verileri rehberde karşı gelen kayda yazıyoruz. 

Sonrasında rehber değişkenini hard diske kaydediyoruz, formu görünmez yapıyoruz ve yukarıdaki listenin en başına scroll yapıyoruz. 

Bitti mi? Bitmedi. Telefon numaraları ya da emaillere ekleme ya da çıkarma yapılmak istenebilir. Önce çıkarmasına bakalım. Kayıtların başlarına çarpı işaretli link koyalım tıklanınca kayıt silinmesi onay ile yapılsın. 

teldef/index.html

            <div class="control mb-0"
                    v-for="(telNo, key) in düzenlenenKayıt.telefonlar">
                <span class="has-background-danger has-text-white
                    has-text-weight-bold is-clickable"
                    @click="deleteTelKey(key)">&times;</span>
....
            <div class="control mb-0"
                    v-for="(email, key) in düzenlenenKayıt.emailler">
                <span class="has-background-danger has-text-white
                    has-text-weight-bold is-clickable"
                    @click="deleteEmailKey(key)">&times;</span>

Telefon numarası ve email giriş satırlarının başına kırmızı çarpı işaretleri gelir.

Şimdi tıklanmalarına karşı gelen metodları yazalım. 

teldef/app.js

    methods: {
        deleteTelKey(key) {
            let text = key + " telefon silinecek emin misin?";
            if (confirm(text) == true) {
                delete this.düzenlenenKayıt.telefonlar[key]
            }
        },
        deleteEmailKey(key) {
            let text = key + " email silinecek emin misin?";
            if (confirm(text) == true) {
                delete this.düzenlenenKayıt.emailler[key]
            }
        },

Bir onay mesajı çıkıyor kullanıcı Tamam butonu tıklarsa o key bilgisi siliniyor. 

Şimdi de görsele yeni telefon numarası ve yeni email adresi için key eklenmesi için ilave yapalım.

teldef/index.html

            <label class="pl-2 has-text-weight-bold has-text-black">
                Telefonlar : </label>
            <a class="has-background-success has-text-white has-text-weight-bold"
                href="" @click.prevent="addTelNo"></a>
....
            <label class="pl-2 has-text-weight-bold has-text-black">
                Emailler : </label>
            <a class="has-background-success has-text-white has-text-weight-bold"
                href="" @click.prevent="addEmail"></a>

Oradaki ✚ işaretleri bildiğimiz + işareti değil Windows Karakter Eşlem uygulamasında Segoe UI Symbol karakterleri arasında buldum, kalın Yunan haçı imiş. Tavsiye ederim çok faydalı karakterler var, bazen boşuna resim arıyoruz. Yalnız özel kullanım olan karakterlerde style="font-family: 'Segoe UI Symbol';"  stili gerekiyor. İsterseniz buradan kopyalayabilirsiniz. 

Yazılıma çok meraklıyım da görsel zevkim hiç olamadı gitti. Metod tanımlarını da yapalım,

teldef/app.js

    methods: {
        addTelNo() {
            let v = this.düzenlenenKayıt.telefonlar
            if (Object.keys(v).length > 0)
                v[Object.keys(v)[Object.keys(v).length - 1] + "_"] = ""
            else
                v["Tel1"] = ""
        },
        addEmail() {
            let v = this.düzenlenenKayıt.emailler
            if (Object.keys(v).length > 0)
                v[Object.keys(v)[Object.keys(v).length - 1] + "_"] = ""
            else
                v["Email1"] = ""
        },

Eğer kendisinden önce o grupta bir key varsa sonuncusunun adına bir _ karakteri ekleyip yeni adı öneriyor. Tüm telefonlar ya da emailler silindiyse de Tel1 ve Email1 key değerleri ile yeni eklemeyi yapıyoruz. 

Artık bitti. Rehberimizi doldurmaya başlayabiliriz. Benden bu kadar, siz artık başka istekleriniz olursa eklersiniz. Bir sürü kod örneği gördük. Belki başka şekillerde de olabilirdi ama ben ne kolayıma gelirse o yoldan giderim. Adım adım, işleye işleye gidilebilen programlama dillerini de çok severim. Bu makalede yaptığımız uygulama da böyle, adım adım ne güzel bitirdik. Tekrar görüşmek ümidiyle , Kalın sağlıcakla..







Hiç yorum yok:

Yorum Gönder