https://buraksenyurt.com/Burak Selim Şenyurt - Vue.js2020-12-18T11:48:04+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://buraksenyurt.com/post/vue-icin-bebek-adimlariVue için Bebek Adımları2020-12-21T08:46:00+00:00bsenyurt<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/vue_manga.png" alt="" align="right" />Yazılım işine girdiğimden beri en çok zorlandığım konu Frontend tarafında kodlama yapmak. Ne yazık ki sadece Backend tarafta kalma lüksümüz de pek bulunmuyor. Örneğin hali hazırda çalışmakta olduğum firmada yeni nesil birçok uygulama önyüz tarafında çeşitli Javascript çatıları<em>(Framework)</em> kullanıyor.</p>
<p>Pratikte bakınca oldukça iyi bir kurgu aslında. Önyüzü Vue, React, Angular vb yapılarla geliştirip, asıl iş kuralları için arka planda yer alan .Net Core Web API servislerine gelmek. C# ve .Net Core tarafına aşina olduğum için arka planı rahatça kodluyorum, önyüz tarafında ise önceden geliştirilmiş sayfalara bakarak bir şeyler yapabiliyorum. Yani işin özü Vue çatısının temellerinde sorunlarım var. Bu amaçla SkyNet'e uğradığım bir gün oturdum ekran başına en basit adımlarıyla bu işi nasıl öğrenirim bir kurcalayayım dedim.</p>
<p>Tabii öncesinde Vue.Js ile ilgili bir bilgi vermek de lazım. Açık kaynak olarak sunulan ilk Commit'i 2013 ve ilk Release'i de 2014 olan Vue, temel olarak Singe Page Application geliştirmek için Model View ViewModel<em>(MVVM)</em> desenini baz alan bir Javascript Framework olarak düşünülebilir. İşi gücü arayüz geliştirilmesini MVVM'in nimetlerinden yararlanarak kolaylaştırmak. Reactive olması, HTML'i direktifler ile genişletmesi ve DOM elementleri ile veriyi, olayları kolayca bağlaması öne çıkan özellikleri arasında sayılabilir.</p>
<p>Bu arada bugüne kadar çıkan sürümlere Manga dizilerinin adları verilmiş. Son sürüm One Piece, 2016 - Ghost in the Shell, 2015 - Dragon Ball, 2014 - Blade Runner, Cowboy Bebop ve Animatrix gibi isimlendirmeler kullanılmış<em>(Peki Animatrix ile başlayan bu Manga adlarının alfabetik sırada gittiğini biliyor muydunuz?)</em> İşin magazin kısmı bir yana kalsın gelin biz basit adımlarla Vue'nun temel kabiliyetlerini tanımaya çalışalım. Bunun için Javascript kütüphaneleri veya CLI araçlarını indirmemize de gerek yok. Temel konular için Vue'nun CDN<em>(Content Delivery Network)</em> kaynağından<em>(<a href="https://unpkg.com/vue">https://unpkg.com/vue</a>)</em> yararlanmamız yeterli.</p>
<p>İlk adımımızda onun ne kadar duyarlı olduğunu<em>(Reaktif)</em> anlamaya çalışalım<em>(Yorum satırlarını okumayı ihmal etmeyin)</em></p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">touch vue_is_reactive.html</pre>
<p>Kodlarımızı aşağıdaki gibi geliştirelim.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><html>
<head>
<title>VueJs Bebek Adımları - 01</title>
<!-- CDN adresinden Vue.js'i kullanacağımızı söyledik-->
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<!--
Vue'da DOM nesneleri ile veri(data) birbirine bağlıdır ve sürekli etkileşim halindedir.
-->
<div id="appComponent" style="text-align:center;">
<h1>Şu An Çalıştığım Kitap</h1>
<p>{{bookName}}</p>
<p>{{startDate}}</p>
<!-- Javascript expression içerisinde aşağıdaki gibi fonksiyon çağrıları da yapılabilir-->
<p>{{bookName.split('').reverse().join('')}}</p>
</div>
<script type="text/javascript">
/*
app bir Vue uygulama nesnesidir.
Parametre olarak çeşitli seçenekleri ihtiva eden bir JSON değişkeni alır.
el(element) niteliği uygulama nesnesini appComponent ismiyle div elementine bağlar .
Vue yapıcı fonksiyonundaki JSON nesnesinin data özelliği içerisinde koyduğumuz alanlar, DOM içerisinde {{ }} ifadelerinin olduğu yerlerde kullanılır.
Mustache stilindeki {{ }} yerler javascript expression olarak adlandırılır. Vue bu ifadeleri gördüğünde, data özelliğindeki karşılıkları ile değiştirir.
Vue'nun nesne verileri(instance data) HTML'de referans edildikleri heryere bağlanır.
Bunu daha iyi anlamak için HTML sayfasını açtıktan sonra F12 ile Developer bölgesine geçin ve Console'da
app.bookName="Learning Vue"
yazın. HTML içeriğinde bookName olan heryerin anında değiştiğini göreceksiniz. İşte bu Reactive olma özelliğidir.
*/
var app = new Vue(
{
el: '#appComponent',
data: {
bookName: "Rust Programming Cookbook",
startDate: "Today"
}
}
)
</script>
</body>
</html></pre>
<p>Oluşturduğumuz HTML sayfasını bir tarayıcıda açtıktan sonra özellikle F12 ile Debug moduna geçip Vue uygulama nesnesi olan app değişkeninin data özelliğindeki bookName içeriğini Console üstünden değiştirmeyi deneyin. Bu değişiklik sayfada bookName'i kullanan tüm elementlere yansıyacaktır. Buradan Vue ana bileşeninin<em>(Component</em>) DOM ile etkileşim halinde olduğunu söyleyebiliriz. İşte bu reaktif olmanın bir sonucudur.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_01.png" alt="" /></p>
<p>ve F12 - Console sonrası. </p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_02.png" alt="" /></p>
<p>İkinci adımımızda Attribute Binding konusunu ele alacağız. HTML elementlerindeki nitelikleri<em>(Örneğin img elementinin src niteliğini)</em> direktifler<em>(Örnekte v-bind)</em> ile Vue verisine<em>(data özelliğinin değerleri)</em> nasıl bağlayacağımızı göreceğiz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">touch vue_attribute_binding.html</pre>
<p>HTML sayfa kodlarını aşağıdaki gibi yazarak devam edelim<em>.</em></p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><html>
<head>
<title>VueJs Bebek Adımları - 02</title>
<!-- CDN adresinden Vue.js'i kullanacağımızı söyledik-->
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<!--
HTML elementlerinin niteliklerini(attribute)'de veriye bağlamak isteyebiliriz.
Örneğin aşağıdaki HTML yapısından yer alan src ve altText niteliklerini, data nesnesinin sırasıyla coverPhoto ve alternativeText alanlarına bağlamak istediğimizi düşünelim.
Bu durumda v-bind isimli yönergeyi(directive) kullanmamız gerekir.
01 nolu örnekte olduğu gibi bu HTML'i açtıktan sonra tarayıcının Console penceresinde
app.coverPhoto="./images/book_2.jpeg"
yazın. Fotoğrafın hemen değiştiğini göreceksiniz. Yani v-bind direktifi ile bağlanan yerlerde veri değişikliğini anında yansıtır.
-->
<div id="app" style="text-align:center;">
<div id="book">
<h1>{{title}}</h1>
<p>{{description}}</p>
</div>
<div id="book-photo">
<!--
v-bind direktifini : operatörü ile daha kısa şekilde de kullanabiliriz.
Yani v-bind:src yerine :src yazılabilir.
-->
<img :src="coverPhoto" v-bind:altText="alternativeText" />
</div>
</div>
<script type="text/javascript">
var app = new Vue(
{
el: '#app',
data: {
title: "Rust Programming Cookbook",
description: "Perfect book about programming with rust",
coverPhoto: "./images/book_1.jpeg",
alternativeText: "Rust Programming Cookbook Cover Photo"
}
}
)
</script>
</body>
</html></pre>
<p>Sayfadaki img elementinin kullandığı resmi kaynağı ve açıklama kısmı Vue bileşeninin data özelliğinden beslenir. Yine F12 Debug moddayken bu içeriklerin değişmesi anında elementlere de yansıyacaktır. Aşağıdaki ekran görüntülerinde olduğu gibi ;)</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_03.png" alt="" /></p>
<p>ve F12 Debug mod durumu.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_04.png" alt="" /></p>
<p>Buraya kadar az çok bir Vue bileşeninin HTML DOM nesneleri ile nasıl konuştuğunu anladık diyebiliriz. Üçüncü adımımızda akış kontrol ifadelerinden if...else kullanımına bakalım.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">touch vue_conditional_render.html</pre>
<p>Kodlarımızı da aşağıdaki gibi geliştirelim.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><html>
<head>
<title>VueJs Bebek Adımları - 03</title>
<!-- CDN adresinden Vue.js'i kullanacağımızı söyledik-->
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<!--
02nci örneğin aynısı ancak bu kez kitabın stoktaki miktarına göre HTML elementlerinin Render edilip edilmeyeceklerini belirliyoruz.
v-if v-else, v-else-if, v-show gibi direktiflerle HTML elementlerinin Render operasyonları koşula bağlanabilir.
stock-state isimli div içerisinde p elementleri, quantity değerine göre görüntülenmektedir.
onDiscount, true veya false değer almaktadır. Bu gibi sıklıkla kapalı veya açık konuma geçecek bir element söz konusu olduğunda v-show direktifinin kullanılması önerilir.
Pek tabii Vue tarafında da switch yapısı mevcuttur.
Yine tarayıcı Console'unda onDiscount ve quantity değerleri ile oynayarak sayfanın nasıl değişikliklere uğradığını inceleyebilirsiniz.
-->
<div id="app" style="text-align:center;">
<div id="book">
<h1>{{title}}</h1>
<p>{{description}}</p>
<div id="stock-state" style="font-weight:bold;">
<p v-if="quantity>100">Depoda yeterli miktarda var. Tam {{quantity}} adet. Sakin!</p>
<p v-else-if="quantity>50 && quantity<100">İdare ederizzzz... {{quantity}}</p>
<p v-else-if="quantity>0 && quantity<50">Imm..Şey. Sipariş etsek mi? Sadece {{quantity}} adet kalmış</p>
<p v-else>Ovv yooo!!! Bu da ne? {{quantity}}</p>
</div>
<p v-show="onDiscount">İndirimde</p>
</div>
<div id="book-photo">
<img :src="coverPhoto" v-bind:altText="alternativeText" />
</div>
</div>
<script type="text/javascript">
var app = new Vue(
{
el: '#app',
data: {
title: "Rust Programming Cookbook",
description: "Perfect book about programming with rust",
coverPhoto: "./images/book_1.jpeg",
alternativeText: "Rust Programming Cookbook Cover Photo",
quantity: 120,
onDiscount: true,
level:"Small"
}
}
)
</script>
</body>
</html></pre>
<p>Sayfada ürünün miktarına göre stock-state altındaki paragraflardan hangisinin gösterileceğine karar veriliyor. Yani verinin durumuna göre bir elementin görünümü, içeriği vs değiştirilebiliyor. İşte örneğe ait çalışma zamanı çıktıları.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_05.png" alt="" /></p>
<p>ve F12 Console'dan quantity ile onDiscount değerlerini değiştirdikten sonraki durum.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_06.png" alt="" /></p>
<p>Çok doğal olarak bu tip bir Vue sayfasında bileşenin kullandığı veri önemlidir. Data özelliğinin içeriği bir servisten çekilmiş bir liste olabilir. Bu durumda veriyi sayfada gösterirken basit for döngülerine ihtiyaç duyarız. Dördüncü adımda bu döngüyü bir JSON dizisi için nasıl kullanacağımızı ele alıyoruz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">touch vue_for_loop.html</pre>
<p>Kodlarımızı aşağıdaki gibi geliştirelim.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><html>
<head>
<title>VueJs Bebek Adımları - 04</title>
<!-- CDN adresinden Vue.js'i kullanacağımızı söyledik-->
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<!--
app nesnesinin data özelliği ile gelen JSON içeriğinde bir liste olduğunu düşünelim.
Örnekte kitap kategorisindeki birkaç ürün bilgisine yer veriliyor.
Bu listeyi sıralı bir şekilde HTML' e yazdırmak için v-for direktifinden yararlanılabilir.
Çalışma zamanında yine Chrome Console'a girilmiş ve anlık olarak books array'indeki değerlerle oynanmıştır.
-->
<div id="app" style="text-align:center;">
<div id="book">
<h1>'{{category}}' Kategorisindeki Ürünler</h1>
<!--
data özelliğindeki books dizisinin herbir elemanı book olarak isimlendirilmiştir.
{{ }} notasyonu ile book üstünden id, title, publisher ve level özelliklerine erişililir.
Döngülerde Render edilen elementlerin tekil bir anahtar ile işaretlenmesi önerilir.
Bunun için :key direktifi kullanlır.
Örnekte <p> elementlerinin id isimli özellik değeri ile tekil(unique) olması sağlanır.
-->
<div v-for="book in books" :key="book.id">
<p>{{book.title}} <i>({{book.publisher}}) - ${{book.listPrice}}</i></p>
</div>
</div>
</div>
<script type="text/javascript">
/*
data özelliği bir JSON nesnesi alabildiğinden içerisinde n elemanlı array'ler de barındırabilir.
*/
var app = new Vue(
{
el: '#app',
data: {
category: "Kitap",
books: [
{ id: 1, title: "Programming C# for Beginners", publisher: "Wrox", listPrice: 19.95, level: 100 },
{ id: 2, title: "Patterns of Enterprise Application Architecture", publisher: "Addison Wesley", listPrice: 34.50, level: 300 },
{ id: 3, title: "Game Engine Architecture", publisher: "Gregory", listPrice: 45.50, level: 300 },
]
}
}
)
</script>
</body>
</html></pre>
<p>Bu adımdan sonraki çalışma zamanı çıktıları ise aşağıdaki gibi olacaktır.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_07.png" alt="" /></p>
<p>ve F12 ile Console'a geçip dizinin elemanlarında değişiklik yaptıktan sonrası.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_08.png" alt="" /></p>
<p>Bir Web sayfası mutlaka kullanıcı ile etkileşim halindedir. Dolayısıyla sayfa üstünde gerçekleştireceği bazı olayların Vue bileşeni tarafında da ele alınması gerekir. Beşinci adımda bunu anlamaya çalışacağız.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">touch vue_event_handling.html</pre>
<p>HTML sayfasının kodları da şöyle.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><html>
<head>
<title>VueJs Bebek Adımları - 05</title>
<!-- CDN adresinden Vue.js'i kullanacağımızı söyledik-->
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<!--
Sayfadaki olaylar v-on direktifi kontrollere bağlanabilir.
Örnekte kitap fiyatını artırmak ve azaltmak için iki button ve click olayları kullanılmaktadır.
-->
<div id="app" style="text-align:center;">
<h1>'{{category}}' Kategorisindeki Ürünler</h1>
<div v-for="book in books" :key="book.id">
<p>
<!--
CSS stillerini de veriye bağlayabiliriz.
span elementinin arkaplan rengini belirleyen backgrounColor değeri o anki book nesnesinin color özelliğine bağlanmıştır.
-->
<span :style="{backgroundColor: book.color}"> </span>
{{book.title}}
<i>({{book.publisher}}) - ${{book.listPrice}}</i>
<!--
Fiyat artırma işini üstlenen click olayı gerçekleştiğinde ifade içerisindeki kod çalışır. Bulunulan book nesnesinin listPrice değeri 1 artar.
-->
<button v-on:click="book.listPrice+=1">+</button>
<!--
Ancak olayları aşağıdaki gibi fonksiyonlara devrederek kullanmak daha doğrudur.
Bu kez button click olayı gerçekleştiğinde book.id değerini alan decreasePrice metodu çağrılır.
Bu metod Vue nesnesinin opsiyonel parametrelerinden olan methods içerisinde tanımlanır.
v-on direktifi aşağıdaki gibi @ ifadesi ile daha kısa şekilde yazılabilir.
Örnekte disabled niteliği de indirim yapılıp yapılmayacağını belirten incAvailable düğmesine bağlanmıştır.
Mesela 2 numaralı ürün için indirim uygulanamaz.
-->
<button @click="decreasePrice(book.id)" :disabled="!book.incAvailable">-</button>
</p>
</div>
</div>
<script type="text/javascript">
var app = new Vue(
{
el: '#app',
data: {
category: "Kitap",
books: [
{ id: 0, title: "Programming C# for Beginners", publisher: "Wrox", listPrice: 19.95, level: 100, incAvailable: true, color: "blue" },
{ id: 1, title: "Patterns of Enterprise Application Architecture", publisher: "Addison Wesley", listPrice: 34.50, level: 300, incAvailable: true, color: "red" },
{ id: 2, title: "Game Engine Architecture", publisher: "Gregory", listPrice: 45.50, level: 300, incAvailable: false, color: "red" },
]
},
methods: {
/*
button click olayı gerçekleştiğinde çalıştırılan metot.
*/
decreasePrice(id) {
console.log(id, ". eleman için 1 dolar indirim");
this.books[id].listPrice -= 1;
}
}
}
)
</script>
</body>
</html></pre>
<p>Kullanıcı kitap fiyatlarını artırıp azaltabilir. Her iki aksiyon için olay bildirimlerinin nasıl yapıldığına dikkat edin. Olayın gerçekleşmesi sonucu çalışacak kod bir direktif ile birlikte yazılabileceği gibi Vue bileşeninin methods özelliği içerisinde de konuşlandırılabilir.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_10.png" alt="" /></p>
<p>Yine F12 - Console penceresinde CSS rengini değiştirecek şekilde veriyle oynayabiliriz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_11.png" alt="" /></p>
<p>Altıncı adımda verinin HTML elementlerinin içeriğine göre bir hesaplamaya dahil edilmesine bakacağız. Burada Vue bileşeninin computed özelliğindeki fonksiyonlar devreye giriyor. Listelenen kitaplardan herhangi birinin üstüne gelindiğinde o kitabın fiyatı güncel döviz kuru değerine göre hesaplatılıp alt tarafta yazılıyor. Burada senaroyu biraz daha zengileştirebilirsiniz. Örneğin fare imleci kitap adının üstüne geldiğinde bir popup içinde resmini ve açıklamasını gösterebilirsiniz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">touch vue_computed_props.html</pre>
<p>Kodlarımızı aşağıdaki gibi geliştirelim.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><html>
<head>
<title>VueJs Bebek Adımları - 06</title>
<!-- CDN adresinden Vue.js'i kullanacağımızı söyledik-->
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="app" style="text-align:center;">
<h1>'{{category}}' Kategorisindeki Ürünler</h1>
<!--
book_count, bir computed property'dir.
-->
<p><i>{{book_count}} adedi satışta</i></p>
<!--
div üzerinde mouse bir kitaba denk geldiğinde bu kitabın indis değeri markIndex isimli fonksiyona gönderilir.
markIndex fonksiyonu gelen bu indis değerini data elementindeki selectedBookIndex'e set eder.
Buna göre computed içerisinde hangi kitap için işlem yapacağımızı anlayabiliriz.
-->
<div v-for="(book,index) in books" :key="book.id" @mouseover="markIndex(index)">
<p>
{{book.title}} <i>${{book.listPrice}}</i>
</p>
</div>
<h3>Güncel kurdan fiyatı {{local_price}} liradır.</h3>
</div>
<script type="text/javascript">
/*
Computed Properties.
app nesnesinin computed özelliğinde local_price isimli bir fonksiyon bulunmaktadır.
Bu fonksiyon selectedBookIndex değerine işaret eden ürünün liste fiyatını güncel döviz kuru ile çarpıp geriye döndürür.
Döndürülen değer HTML içerisinde for döngüsünün dışındaki bir h3 elementine basılır.
*/
var app = new Vue(
{
el: '#app',
data: {
category: "Kitap",
books: [
{ id: 0, title: "Programming C# for Beginners", publisher: "Wrox", listPrice: 19.95, onSale: true },
{ id: 1, title: "Patterns of Enterprise Application Architecture", publisher: "Addison Wesley", listPrice: 34.50, onSale: true },
{ id: 2, title: "Game Engine Architecture", publisher: "Gregory", listPrice: 45.50, onSale: false },
],
selectedBookIndex: 0,
},
methods: {
markIndex(index) {
this.selectedBookIndex = index;
//console.log(index);
}
},
computed: {
/*
Computed Property'ler hesaplamaya dahil ettikleri veriler değişmediği sürece cache üzerinde tutulurlar.
local_price, mouseover hareketine göre üzerinde durulan kitabı alıp liste fiyatını bir işlemden geçirir ve geriye bir değer döner.
book_count ise satışta olan kitapların adedini bulur ve geriye döner.
*/
local_price() {
//console.log(this.selectedBookIndex);
return this.books[this.selectedBookIndex].listPrice * 8.15;
},
book_count() {
count = 0;
for (i = 0; i < this.books.length; i++) {
if (this.books[i].onSale)
count++;
}
return count;
}
}
}
)
</script>
</body>
</html></pre>
<p>Bu örneğin çalışma zamanı çıktısı ise aşağıdaki gibi olacaktır.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_12.png" alt="" /></p>
<p>Buraya kadarki adımlarda hep ana Vue bileşeni ile çalıştık. Çok doğal olarak HTML DOM yapısının birden fazla Vue bileşeni ile çalışması da istenebilir. Nitekim bir süre sonra ana bileşen çok fazla kalabalıklaşır. Yedinci adımda bu durumu anlamaya çalışacağız. </p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">touch vue_components.html</pre>
<p>Örnekte yer alan sportnews ve book iki ayrı Vue bileşeni olarak tasarlanmıştır. Template olarak birer div döndürdüklerine dikkat edelim. HTML'de konuşlandırılan aynı isimli elementler içerisine bu şablonlar basılır. book bileşenindeki iLikeIt olayının kullanımı da önemli. book bir alt bileşen olarak üzerinde gerçekleşen olay sonrası app isimli ana bileşeni de uyarmaktadır. Yani alt bileşene ait bir olay tetiklendiğinde üst bileşende de bir olay tetiklenmesini sağlayabiliriz. Dolayısıyla bileşenler birbirleriyle olaylar üzerinden de iletişim kurabilirler.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><html>
<head>
<title>VueJs Bebek Adımları - 07</title>
<!-- CDN adresinden Vue.js'i kullanacağımızı söyledik-->
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="app" style="text-align:left;color:white">
<sportnews></sportnews>
<!--
Bileşenleri aşağıdaki gibi de kullanabiliriz. book isimli bileşenin verisi app bileşenindeki data içerisinde yer alan json dizisidir.
book elementi içerisinde :property_name şeklindeki tanımlamalar ile döngünün dolaştığı book nesnesinin değerleri bileşen içerisine aktarılır.
:property_name bilgileri book bileşeninin props özelliğinde tanımlanmıştır.
Ek: Alt bileşenden üst bileşene bildirim yollamak.
Amaç bir kitabın "Beğendim" butonuna basıldığında app bileşenindeki(üst component) toplam beğeni sayısını artırmak.
Bunun için book bileşeninde düğmeye basıldığında, üst bileşene bunu bildirecek şekilde bir mesaj göndermek gerekir.
Bu mesaj $emit fonksiyonu ile yollanabilir(book içerisindeki iLikeIt metoduna bakın)
@i-like-it niteliğinde belirtilen updateLikeCount fonksiyonu ise alt bileşenlerden birisi i-like-it mesajını yukarı fırlattığında çağırılır.
Bu arada hangi book bileşenine basıldığını anlamak için (b,index) çiftindeki index değerini updateLikeCount metoduna parametre olarak verebiliriz.
-->
<div v-for="(b,index) in books" :key="b.id">
<book :book_title="b.title" :book_summary="b.description" :book_authors="b.authors"
:book_list_price="b.listPrice" @i-like-it="updateLikeCount(index)">
</book>
</div>
<p style="color: purple;">Toplamda {{likeCount}} kere beğen düğmelerine bastınız!</p>
</div>
<script type="text/javascript">
/*
Bu örneğe kadar dikkat edileceği üzere app nesnesinin data, computed, methods gibi özelliklerinin kalabalıklaşmaya başladığını gördük.
Yönetilebilir ve modüler yapıdaki bir Vue sayfası için bileşenler(component) kullanmak doğru bir yaklaşımdır.
Yani ana sayfadaki component'in alt bileşenlerden oluştuğunu düşünebiliriz.
Örnekte iki component tanımlanmış ve app isimli div içerisinde kullanılmıştır.
sportnews isimli bileşen oldukça sıradandır. Kendi verisini kullanır.
book isimli bileşen ise app bileşenindeki data içeriğini kullanır.
Bileşenler component fonksiyonu ile tanımlanır.
Her bileşen bir template kullanmalıdır.
template özelliği bir container döndürmelidir(div gibi)
*/
Vue.component('sportnews', {
template:
`
<div class='sportnews' style='text-align:left;backgroundColor:purple;'>
<h2>Günün Öne Çıkan Spor Haberi</h2>
<p><h3>{{title}}</h3></p>
<p>{{summary}}</p>
</div>
`,
data() {
return {
title: "Shane Larkin Milli Takıma Çağırıldı",
summary: "Bir süredir ülkesi ABD'de olan Shane Larkin, Anadolu Efes kampına döndükten sonra doğrudan milli takıma çağırıldı."
}
}
});
Vue.component('book', {
template:
`
<div class='book' style='text-align:right;backgroundColor:gold;color:purple'>
<p><h3>{{book_title}}</h3></p>
<p>{{book_summary}}, {{book_authors}}<br/>{{book_list_price}} TL</p>
<button v-on:click="iLikeIt">Beğendim</button>
</div>
`,
methods: {
iLikeIt() {
console.log('book bileşeninin iLikeIt olayı çağrıldı');
/*
$emit ile button click olayı tetiklendiğinde üst bileşene i-like-it olayı gerçekleşti şeklinde bir bilgi yollanır.
*/
this.$emit('i-like-it');
}
},
props: {
book_title: {
type: String,
required: true
},
book_summary: {
type: String,
required: true
},
book_authors: {
type: String,
required: true
},
book_list_price: {
type: Number,
required: true
},
}
});
var app = new Vue(
{
el: '#app',
data: {
likeCount: 0, // Alt bileşenlerdeki düğmeye basıldığında bu değeri artırıyoruz
books: [
{
id:1001,
title: "Veba",
description: "Camus adı çoğu okur için Yabancı romanıyla özdeşleşir. Ancak yazarın en önemli yapıtı aslında Veba'dır...",
authors: "Albert Camus",
listPrice: 34
},
{
id:1002,
title: "Mahur Beste",
description: "Mahur Beste'de Tanpınar'ın Huzur ve Sahnenin Dışındakiler adlı romanlarında önemli bir motif olan 'Mahur Beste' teması önemli yer tutar. Mahur Beste, acı bir aşk hikayesinin klasik musiki kalıplarıyla soyutlanmasıdır...",
authors: "Ahmet Hamdi Tanpınar",
listPrice: 23
},
{
id:1003,
title: "1Q84",
description: "Sarsıcı bir yolculuğa hazır mısınız? Öyleyse kemerlerinizi bağlayın. Erkekleri, titizlikle geliştirdiği bir yöntemle öteki dünyaya gönderen genç bir kadınla tanışacaksınız. Ve amansız bir takiple onun peşine düşen fanatik bir cemaatin müritleriyle…",
authors: "Haruki Murakami",
listPrice: 23
},
{
id:1004,
title: "Beden Kayıt Tutar",
description: "Ne yazık ki şimdiki psikiyatri anlayışı, yakınmalarınızı anlatmanız ve hekimin de bu yakınmaları düzeltecek bir ilaç önermesi üzerine kurulu. Ancak 'Hiç bir ilaç, kötü geçmiş bir çocukluğu düzeltmiyor'...",
authors: "Bessel A. van der Kolk",
listPrice: 41,
}
]
},
methods: {
/*
Alt bileşenin emit ile gönderdiği mesaj sonrası tetiklenen metot
*/
updateLikeCount(index) {
selected_title = this.books[index].title;
console.log("`", selected_title, "` isimli kitabı beğendin");
console.log('Üst bileşen(app) için updateLikeCount olayı çağırıldı');
this.likeCount += 1;
}
}
}
);
</script>
</body>
</html></pre>
<p>Bu örneğe ait çıktıları aşağıda görebilirsiniz. Gözleriniz kanayabilir o nedenle güneş gözlüğü kullanmanızı ya da monitörden beş metre kadar uzaklaşıp kısık gözle bakmanızı rica ederim :D </p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_15.png" alt="" /></p>
<p>Yine F12 - Console üstünde oynayıp veri değişimlerini izlemekte yarar var.</p>
<p>Bu tip kullanıcı etkileşimli sayfalarda bir diğer konu ise Form kullanımıdır. Yani Form verisi ile Vue tarafı nasıl haberleşebilir, POST edilen veri nasıl ele alınabilir sekizince ve son adımda bunu anlamaya çalışacağız.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">touch vue_forms.html</pre>
<p>Son sayfamıza ait kodları aşağıdaki gibi yazabiliriz.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><html>
<head>
<title>VueJs Bebek Adımları - 08</title>
<!-- CDN adresinden Vue.js'i kullanacağımızı söyledik-->
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="app" style="text-align:left">
<!--
@new-book-created, book-form bileşeninin onSubmit olayı içerisinden yapılan bildirimin adıdır.
Bu bildirim gerçekleştiğinde üst bileşen yeni bir kitap üretildiğini anlayabilir ve bu nesneyi kendi data nesnesindeki books isimli diziye ekleyebilir.
Bunun için addBookToList metodu kullanılır.
-->
<book-form @new-book-created="addBookToList"></book-form>
<div v-for="(b,index) in books" :key="b.id">
<p>
<h2>{{b.title}} ({{b.like}} beğeni)</h2>
</p>
<p>{{b.summary}}</p>
</div>
</div>
<script type="text/javascript">
/*
Örnekte bir form kullanılarak Submit işlemi ele alınıyor.
input, textarea, select gibi girdi elemanları v-model direktifi yardımıyla data fonksiyonundan döndürülen alanlara bağlanırlar.
select kontrolünde kullanılan .number, option içeriğinin integer olarak dönüştürülmesini sağlar.
Submit işlemi gerçekleştiğinde @submit.prevent ile belirtilen onSubmit metodu tetiklenir.
Form Validation için onSubmit metodunda bir takım tedbirler aldık.
Bu arada HTML 5 için required ifadesi ile elementlerin zorunlu hale getirilebileceğini de belirtelim.
*/
Vue.component('book-form', {
template:
`
<div class='book'>
<form class="new-book-form" @submit.prevent="onSubmit">
<p>
<label for="title">Kitabın adı nedir?</label>
<input id="title" v-model="title" placeholder="title">
</p>
<p>
<label for="summary">Düşüncelerin neler?</label>
<textarea id="summary" v-model="summary"></textarea>
</p>
<p>
<label for="like">Ne kadar beğendin?</label>
<select id="like" v-model.number="like">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</p>
<p>
<input type="submit" value="Ekle">
</p>
<p v-if="errors.length" style="color:red;">
<b>Hata : </b>
<ul>
<li v-for="err in errors">{{ err }}</li>
</ul>
</p>
</form>
</div>
`,
data() {
return {
title: null,
summary: null,
like: null,
errors: [] // Form doğrulama hatalarını tutmak için eklendi
}
},
methods: {
/*
Submit düğmesine basılınca tetiklenir.
this ile bu bileşenden gelen name, summary, like gibi alanlar ele alınabilir.
Bu değerler kullanılarak yeni bir nesne oluşturulur.
*/
onSubmit() {
this.errors = [];
if (this.title && this.summary) {
let newBook = {
title: this.title,
summary: this.summary,
like: this.like
}
/*
Üst bileşene yeni bir girdi oluşturulduğuna dair bilgiyi yine $emit ile gönderebiliriz.
İkinci parametre ile oluşturulan nesne örneği üst bileşene yollanır.
*/
this.$emit('new-book-created', newBook)
/*
newBook örneklendikten sonra bu bileşenin verisi temizlenir ve yeni veri girişine uygun hale getirilir.
*/
this.title = null
this.summary = null
this.like = null
} else {
/*
Doğrulama için koyduğumuz kısım.
Eğer başlık veya özet girilmemişse bunla ilgili olarak bu bileşenin errors dizisine bilgi ekliyoruz.
*/
if (!this.title) this.errors.push("Kitap başlığı girilmeli.")
if (!this.summary) this.errors.push("Kitap için geri bildirim eklenmeli.")
}
}
}
});
var app = new Vue(
{
el: '#app',
data: {
books: []
},
methods: {
addBookToList(book) {
/*
book-form bileşeninde Submit işlemi ile bir eleman eklendiğinde @new-book-created bildirimine göre bu metot çağrılır.
book parametresi ile gelen nesne, push fonksiyonu ile books dizisine eklenir.
*/
this.books.push(book)
}
}
}
);
</script>
</body>
</html></pre>
<p>İlk denemelere ait bir ekran çıktısını aşağıda bulabilirsiniz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_16.png" alt="" /></p>
<p>Doğrulama ile ilgili kodların çıktısı da şöyle.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_17.png" alt="" /></p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2020/skynet/41/Screenshot_18.png" alt="" /></p>
<p>Örnekte ekrandan girilen kitap bilgileri books isimli JSON dizisine ekleniyor. Tahmin edileceği üzere bu veri erişimi bir servise doğru yapılmalı. Yani uygulamanın bir servis üzerinden bir veritabanı ile konuşması daha doğru olacaktır. Burada veritabanı hayal gücünüze kalmış. Senaryoya uygun bir NoSQL veya ilişkisel veritabanı kullanılabilir.</p>
<p>Sekizinci örnekle birlikte Vue'nun en temel parçalarını bebek adımları ile biraz olsun incelemiş olduk. Konuyu kendi kendinize çalışabileceğinizi düşünerek sizlere birkaç soru ve ödev bırakıyorum.</p>
<h2>Bomba Sorular</h2>
<ul>
<li>Vue'da v-switch direktifi var mıdır? Yoksa bile kullanmanın bir yolu olabilir mi?</li>
<li>vue_event_handling örneğinde tek bir karakter ekleyerek oluşacak bug'ı bulun.</li>
<li>Vue.component ile bileşen tanımlanırken computed, methods özelliklerini kullanabilir miyiz?</li>
<li>vue_components.html örneğinde yer alan data neden bir fonksiyon şeklinde tanımlanmıştır?</li>
<li>"Props'lar üst bileşenden alt bileşene veri aktarımında kullanılırlar" ifadesi doğru mudur?</li>
</ul>
<h2>ve Ödevler</h2>
<ul>
<li>vue_attribute_binding.html örneğinde kitap fotoğrafına bir link bağlayın<em>(a href)</em> ve href niteliğinin data nesnesindeki url isimli bir özellikten beslenmesini sağlayın.</li>
<li>vue_conditional_render.html örneğinde, level değişkeninin Small, Medium, Large, XLarge olmasına göre sayfanın sağ üst köşesinde S,M,L,XL harflerinin şöyle janjanlı imajlar şeklinde görünmesini sağlayın.</li>
<li>vue_for_loop örneğinde yer alan level değerini kullanarak kitap fontlarını renklendirmeyi deneyin. 100 için farklı bir renk, 300 için farklı bir renk vb</li>
<li>vue_event_handling örneğinde fiyat azaltmada 0 ve eksi değere geçilmesini önleyin. Ayrıca her ürün fiyatı için bir üst artırma limiti olsun ve artışlar bu değeri geçemesin.</li>
<li>Vue antrenmanı yaptığınız herhangi bir sayfada yine ürünleri listeleyin ancak bir ürün adının üstüne geldiğinizde ürünün fotoğrafının olduğu bir div elementini aktif hale getirin. Yani ürün adı üstüne gelince fotoğraf gösterilmesini sağlayın<em>(Popup ile uğraşmayın, sayfadaki bir div alanı görünür hale gelsin yeterli)</em></li>
<li>Okduğunuz son beş kitabın sadece başlıklarını listeleyen bir bileşen tasarlayın. Bu bileşende her başlık yanında "Detay" isimli bir Button olsun. Bu düğmeye basınca kitapla ilgili detayları içeren başka bir bileşen başlığın hemen altında görünür olsun.</li>
<li>Vue_forms.html örneğinde kitap ekledikçe bunu ekrana listeleyen bir bileşeni for döngüsü yardımıyla kullanmayı deneyiniz.</li>
<li>Vue_forms.html örneğinde summary için maksimum 250 karakter girilmesine izin veren bir doğrulama fonksiyonelliği geliştirin.</li>
</ul>
<p>Örnek kodlara <a href="https://github.com/buraksenyurt/skynet/tree/master/No%2041%20-%20Vueeeee" target="_blank">github reposu üzerinden</a> erişebilirsiniz. Böylece geldik bir SkyNet derlememizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2020-12-21T08:46:00+00:00javascriptvuevue.jsFrameworkfrontendhtmlmvvmprogramlamabsenyurtYazılım işine girdiğimden beri en çok zorlandığım konu Frontend tarafta kodlama yapmak. Ne yazık ki sadece Backend tarafta kalmak lüksümüz de pek bulunmuyor. Örneğin hali hazırda çalışmakta olduğum firmada yeni nesil birçok uygulama önyüz tarafında çeşitli Javascript çatıları(Framework) kullanılıyor. Pratikte bakınca oldukça iyi bir kurgu aslında. Önyüzü Vue, React vb yapılarla geliştirip, asıl iş kuralları için arka planda yer alan .Net Core Web API servislerine gelmek. C# ve .Net Core tarafına aşina olduğum için arka planı rahatça kodluyorum, önyüz tarafında ise önceden geliştirilmiş sayfalara bakarak bir şeyler yapabiliyorum. Yani işin özü Vue.Js çatısının temellerinde sorunlarım var. Bu amaçla SkyNet'e uğradığım bir gün oturdum ekran başına en basit adımlarıyla bu işi nasıl öğrenirim bir kurcalayayım dedim.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=6c523aae-c598-43b9-9ad8-326713db185a0https://buraksenyurt.com/trackback.axd?id=6c523aae-c598-43b9-9ad8-326713db185ahttps://buraksenyurt.com/post/vue-icin-bebek-adimlari#commenthttps://buraksenyurt.com/syndication.axd?post=6c523aae-c598-43b9-9ad8-326713db185ahttps://buraksenyurt.com/post/hasura-graphql-engine-ile-gelistirilmis-bir-api-servisini-vue-js-ile-kullanmakHasura GraphQL Engine ile geliştirilmiş bir API Servisini Vue.js ile Kullanmak2019-11-06T10:30:00+00:00bsenyurt<p><img style="float: right;" src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/hasuralogo.png" alt="" />Yıl 2015. Hindistan'ın Bengaluru şehrinde doğan bir Startup<em>(Sonradan San Fransico'da da bir ofis sahibi olacaklar)</em>, <a href="https://blogs.technet.microsoft.com/bizspark_featured_startups/2017/09/18/quickly-develop-backend-applications-without-having-to-write-code-with-hasura/" target="_blank">Microsoft'un BizSpark programından destek</a> buluyor. Kurucuları Rajoshi Ghosh<em>(Aslen bioinformatik araştırmacısı)</em> ve Tanmai Gopal<em>(Bulut sistemleri, fonksiyonel programlama ve GraphQL konusunda uzman)</em> isimli iki Hintli. Şirketlerine şeytanın sanskritçedeki adını veriyorlar; Hasura! Aslında O, fonksiyonel dillerin kralı Haskell ile yazılmış bir platform ve şimdilerde Heroku ile daha yakın arkadaş.</p>
<p>Ekibin amacı geliştiricilerin hayatını kolaylaştıracak, yüksek hızlı, kolayca ölçeklenebilir, sade ve Kubernetes ile dost PaaS<em>(Platform as a Service)</em> ile BaaS<em>(Back-end as a Service)</em> ortamları sunmak.</p>
<p>İsimlendirmenin gerçek hikayesi tam olarak nedir bilemiyorum ama eğlenceli bir logoları olduğu kesin :) Startup'ların en sevdiğim yanlarından birisi de bu. Özgün, tabulara takılmadan, etki bırakacak şekilde düşünülen isimleri, renkleri, logoları...Belki de arka planda sessiz sedasız süreçler çalıştıran back-end servislerini birer iblis olarak düşündüklerinden bu ismi kullanmışlardır. Hatta Heroku üzerinde koştuğunu öğrenince Japonca bir kelime olduğunu bile düşünmüştüm. Hu novs?! Ama işin özü verdikleri önemli hizmetler olduğu. Bunlardan birisi de GraphQL motorları.</p>
<p>API'ler için türlendirilmiş<em>(typed)</em> sorgulama dillerinden birisi olarak öne çıkan GraphQL'e bir süredir uğramıyordum. Daha doğrusu GraphQL sorgusu çalıştırılabilecek şekilde API servis hazırlıklarını yapmaya üşeniyordum. Bu nedenle işi kolaylaştıran ve Heroku üzerinden sunulan <a href="https://hasura.io/" target="_blank">Hasura GraphQL Engine</a> hizmetine bakmaya karar vermiştim. Hasura, veriyi PostgreSQL kullanarak saklıyor ve ayrıca API'yi bir Docker Container içerisinden sunuyor. Amacım Hasura tarafında hazırlayacağım iki kobay veri setini, Vue.js tabanlı bir istemcisinden tüketmekti. Basitçe listeleme ve veri ekleme işlerini yapabilsem başlangıç için yeterli olacaktı. Öyleyse ne duruyoruz. <a href="https://github.com/buraksenyurt/saturday-night-works/tree/master/No%2037%20-%20Hasura%20with%20Vue" target="_blank">37nci saturday-night-works çalışması</a>nın derlemesine başlayalım. İlk olarak Hasura servisini hazırlayacağız.</p>
<h2>Hasura GraphQL Engine Tarafının Geliştirilmesi</h2>
<p>Pek tabii Heroku üzerinde bir hesabımızın olması gerekiyor. Sonrasında <a href="https://elements.heroku.com/" target="_blank">şu adrese</a> gidip elements kısmından Hasura GraphQL Engine'i seçmek yeterli.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_1.png" alt="" /></p>
<p>Gelinen yerden <strong>Deploy to Heroku</strong> diyerek projeyi oluşturabiliriz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_2.png" alt="" /></p>
<p>Ben aşağıdaki bilgileri kullanarak bir proje oluşturdum.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_3.png" alt="" /></p>
<p>Deploy başarılı bir şekilde tamamlandıktan sonra,</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_4.png" alt="" /></p>
<p>View seçeneği ile yönetim paneline geçebiliriz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_5.png" alt="" /></p>
<p>Dikkat edileceği üzere GraphQL sorgularını çalıştırabileceğimiz bir arayüz otomatik olarak sunuluyor. Ancak öncesinde örnek veri setleri hazırlamalıyız. Bunun için Data sekmesinden yararlanabiliriz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_6.png" alt="" /></p>
<p>Arabirimin kullanımı oldukça kolay. Ben aşağıdaki özelliklere sahip tabloları oluşturdum.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_7.png" alt="" /></p>
<p>categories isimli tablomuzda unique tipte, get_random_uuid() fonksiyonu ile eklenen satır için rastgele üretilen categoryId ve text tipinden title isimli alanlar bulunuyor. categoryId, aynı zamanda primary key türünden bir alan.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_8.png" alt="" /></p>
<p>products tablosunda da UUID tipinden productId, text tipinden description, number tipinden listPrice ve yine UUID tipinden categoryId isimli alanlar mevcut. categoryId alanını, ürünleri kategoriye bağlamak için<em>(foreign key relations)</em> kullanıyoruz. Ama bu alanı foreign key yapmak için Modify penceresine geçmeliyiz. </p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_9.png" alt="" /></p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_10.png" alt="" /></p>
<p>İlişkinin geçerlilik kazanması içinse, categories tablosunun Relationships penceresine gidip önerilen bağlantıyı eklemek gerekiyor. </p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_14.png" alt="" /></p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_15.png" alt="" /></p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_16.png" alt="" /></p>
<blockquote>
<p>Bu durumda categories üzerinden products'a gidebiliriz. Ters ilişkiyi de kurabiliriz ve bir ürünle birlikte bağlı olduğu kategorinin bilgisini de yansıtabiliriz ki ürünleri çektiğimizde hangi kategoride olduğunu da göstermek güzel olur. Bunu nasıl yapabileceğinizi bir deneyin isterim.</p>
</blockquote>
<p>Hasura'nın Postgresql tarafındaki örnek tablolarımız hazır. İstersek Insert Row penceresinden tablolara örnek veri girişleri yapabilir ve GraphiQL pencresinden sorgular çalıştırabiliriz. Ben yaptığım denemelerle alakalı bir kaç örnek ekran görüntüsü paylaşayım. Arabirimin sağ tarafında yer alan Docs menüsüne de bakabilirsiniz. Burada query ve mutation örnekleri, hazırladığımız veri setleri için otomatik olarak oluşturuluyorlar.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_11.png" alt="" /></p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_12.png" alt="" /></p>
<h2>Örnek Sorgular</h2>
<p>Veri setimizi oluşturduktan sonra arabirim üzerinden bazı GraphQL sorgularını deneyebiliriz. Ben aşağıdaki örnekleri denedim.</p>
<p><strong>Kategorilerin başlıklarını almak.</strong></p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">query{
categories{
title
}
}</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_19.png" alt="" /></p>
<p><strong>Kategorilere bağlı ürünleri çekmek.</strong></p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">query{
categories{
title
products{
description
listPrice
}
}
}</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_18.png" alt="" /></p>
<p><strong>Ürünlerin tam listesi ve bağlı olduğu kategori adlarını çekmek.</strong></p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">query{
products{
description
listPrice
category{
title
}
}
}</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_17.png" alt="" /></p>
<p>Listeleme işlemleri dışında veri girişi de yapabiliriz. Bunun için mutation kullanıldığını daha önceden öğrenmiştim. Örneğin yeni bir kategoriyi aşağıdaki gibi ekleyebiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">mutation {
insert_categories(objects: [{
title: "Çorap",
}]) {
returning {
categoryId
}
}</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_20.png" alt="" /></p>
<p>Hasura, GraphQL API’si arkasında PostgreSQL veri tabanını kullanırken SQLden aşina olduğumuz bir çok sorgulama metodunu da hazır olarak sunar. Örneğin fiyatı 300 birimin üstünde olan ürünleri aşağıdaki sorgu ile çekebiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">{
products(where: {listPrice: {_gt: 300}}) {
description
listPrice
category {
title
}
}
}</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_21.png" alt="" /></p>
<p>Where metodu sorgu şemasına otomatik olarak eklenmiştir. _gt tahmin edileceği üzere greater than anlamındadır. Yukarıdaki sorguya fiyata göre tersten sıralama opsiyonunu da koyabiliriz. Sadece where koşulu arkasından order_by çağrısı yapmamız yeterlidir.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">{
products(where: {listPrice: {_gt: 300}}, order_by: {listPrice: desc}) {
description
listPrice
category {
title
}
}
}</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_22.png" alt="" /></p>
<p>Çok büyük veri setleri düşünüldüğünde ön yüzler için sayfalama önemlidir. Bunun için limit ve offset değerlerini kullanabiliriz. Örneğin 5nci üründen itibaren 5 ürünün getirilmesi için aşağıdaki sorgu kullanılabilir.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">{
products(limit: 5, offset: 5) {
description
listPrice
category {
title
}
}
}</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_23.png" alt="" /></p>
<p>Hasura Query Engine’in sorgu seçenekleri ile ilgili olarak <a href="https://docs.hasura.io/1.0/graphql/manual/queries/index.html" target="_blank">buradaki dokümanı</a> takip edebilirsiniz.</p>
<h2>İstemci(Vue) Tarafı</h2>
<p>Gelelim ilgili servisi tüketecek olan istemci uygulamamıza. İstemci tarafını basit bir Vue projesi olarak geliştirmeye karar vermiştim. Aşağıdaki terminal komutunu kullanıp varsayılan ayarları ile projeyi oluşturabiliriz. Ayrıca GraphQL tarafı ile konuşabilmek için gerekli npm paketlerini de yüklememiz gerekiyor. Apollo<em>(ilerleyen ünitelerde ondan bir GraphQL Server yazmayı denemiştim),</em> GraphQL servisimiz ile kolay bir şekilde iletişim kurmamızı sağlayacak. Görsel taraf içinse bootstrap kullanabiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">sudo vue create nba-client
sudo npm install vue-apollo apollo-client apollo-cache-inmemory apollo-link-http graphql-tag graphql bootstrap --save</pre>
<h2>Kod Tarafı</h2>
<p>Vue uygulaması tarafında yapacaklarımız kabaca şöyle<em>(Kod dosyalarındaki yorum bloklarında daha detaylı bilgiler mevcut)</em></p>
<p>Components klasörüne tek ürün için kullanılabilecek ProductItem isimli bir bileşen ekliyoruz. Anasayfa listelemesinde tekrarlanacak türden bir bileşen olacak bu. Bileşende product özelliği üzerinden içerideki elementlere veri bağlama işlemini gerçekleştirmekteyiz. {{nesne.özellik}} notasyonlarının nasıl kullanıldığına dikkat edelim.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><template>
<div :key="product.productId" class="card w-75">
<div class="card-header">
<p class="card-text">{{product.description}}</p>
</div>
<div class="card-body text-left">
<h6 class="card-subtitle mb-2 text-muted">{{product.listPrice}} Lira</h6>
<h6 class="card-subtitle mb-2">'{{product.category.title}}' kategorisinden</h6>
</div>
<div class="card-footer text-right">
<a href="#" class="btn btn-primary">Sepete Ekle</a>
</div>
<hr/>
</div>
</template>
<script>
export default {
name: "ProductItem",
props: ["product"]
};
</script></pre>
<p>Ürünlerin listesini gösterebilmek içinse ProductList isimli bir bileşen kullanacağız. Bunu da components altında aşağıdaki gibi yazabiliriz.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><template>
<div>
<!--
products dizisindeki her bir ürün için product-item öğesi ekliyoruz.
Bu öğe bir ProductItem bileşeni esasında
-->
<product-item v-for="product in products" :key="product.productId" :product="product"></product-item>
</div>
</template>
<script>
/*
div içerisinde kullandığımız product-item elementi için ProductItem bileşenini eklememi gerekiyor.
gql ise GraphQL sorgularını çalıştırabilmemiz için gerekli
*/
import ProductItem from "./ProductItem";
import gql from "graphql-tag";
/*
GraphQL sorgumuz.
Tüm ürün listeini, kategori adları ile birlikte getirecek
*/
const selectAllProducts = gql`
query getProducts{
products{
productId
description
listPrice
category{
title
}
}
}
`;
/*
Sorguyu GraphQL API'sine gönderebilmek için apollo'ya ihtiyacımız var.
products dizisini query parametresine verilen değişken ile çekiyoruz.
*/
export default {
name: "ProductList",
components: { ProductItem }, // Sayfada bu bileşeni kullandığımız için eklendi
data() {
return {
products: []
};
},
apollo: {
products: {
query: selectAllProducts
}
}
};
</script></pre>
<p>Ürün ekleme işini ProductAdd isimli bileşen üstleniyor. Yine components sekmesinde konuşlandıracağımız tipin kod içeriği aşağıdaki gibi olmalı.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><template>
<!-- Veri girişi için basit bir formumuz var. Input değerlerini v-model niteliklerine verilen isimlerle bileşene bağlıyoruz -->
<form @submit="submit">
<fieldset>
<div class="form-group w-75">
<input
class="form-control"
aria-describedby="descriptionHelp"
type="text"
placeholder="Ürün bilgisi"
v-model="description"
>
<small
id="descriptionHelp"
class="form-text text-muted"
>Satışı olan basketbol malzemesi hakkında kısa bir bilgi...</small>
</div>
<div class="form-group w-75">
<input class="form-control" type="number" v-model="listPrice">
<small id="listPriceHelp" class="form-text text-muted">Ürünün mağaza satış fiyatı</small>
</div>
<div class="form-group w-75">
<input
class="form-control"
type="text"
placeholder="Halledene kadar kategorinin UUID bilgisi :D"
v-model="categoryId"
>
</div>
<!-- Kategoriyi drop down olarak nasıl ekleyebiliriz? -->
</fieldset>
<div class="form-group w-75 text-right">
<button class="btn btn-success" type="submit">Dükkana Yolla</button>
</div>
</form>
</template>
<script>
import gql from "graphql-tag";
//import { InMemoryCache } from "apollo-cache-inmemory";
/*
Bu veri girişi yapmak için kullanacağımız mutation sorgumuz.
insert_products'u Hasura tarafında kullanmıştık hatırlarsanız.
mutation parametrelerini belirlerken veri türlerine dikkat etmemiz lazım.
Söz gelimi listPrice, Hasura tarafında Numeric tanımlandı. CategoryId değeri
ise UUID formatında. Buna göre case-sensitive olarak veri tiplerini söylüyoruz.
Aslında bunu anlamak için numeric! yerine Numeric! yazıp deneyin. HTTP 400
Bad Request alıyor olmalısınız.
*/
const addNewProduct = gql`
mutation addProduct(
$description: String!
$listPrice: numeric!
$categoryId: uuid!
) {
insert_products(
objects: [
{
description: $description
listPrice: $listPrice
categoryId: $categoryId
}
]
) {
returning {
productId
}
}
}
`;
export default {
name: "ProductAdd",
data() {
return {
description: "",
listPrice: 0,
categoryId: ""
};
},
apollo: {},
methods: {
/*
form submit edildiği zaman devreye giren metodumuz.
$data ile formdaki veri içeriğini description, listPrice ve categoryId olarak yakalıyoruz
*/
submit(e) {
e.preventDefault();
const { description, listPrice, categoryId } = this.$data;
/*
apollo'nun mutate metodu ile addNewProduct isimli mutation sorgusunu çalıştırıyoruz.
Sorgunun beklediği değişkenler this.$data ile zaten yakalanmışlardı.
*/
this.$apollo.mutate({
mutation: addNewProduct,
variables: {
description,
listPrice,
categoryId
},
refetchQueries: ["ProductList"] // Insert işlemini takiben ürün lstesini tekrardan talep ediyoruz
});
}
}
};
</script></pre>
<p>Uygulamanın ana bileşeni olan App.Vue'da product-add ve product-list isimli nesnelerimizi aşağıdaki gibi yerleştirebiliriz.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><template>
<div id="app">
<h2 class="text-left">Yeni Ürün</h2>
<!-- Bileşenleri altalta dizdik -->
<product-add/>
<h2 class="text-left">Basketbol Ürünleri</h2>
<product-list/>
</div>
</template>
<script>
/*
Ana bileşen içerisinde kullanılan alt bileşenlerin import edilmesi
*/
import ProductList from "./components/ProductList.vue";
import ProductAdd from "./components/ProductAdd.vue";
export default {
name: "app",
components: {
ProductList,
ProductAdd
}
};
</script></pre>
<p>Main.js içerisinde de önemli kodlamalarımız var. Amaç Hasura'yı ve GraphQL'i kullanabilir hale getirmek. Kodlarını aşağıdaki gibi geliştirebiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import Vue from 'vue';
import App from './App.vue';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import 'bootstrap/dist/css/bootstrap.min.css';
import VueApollo from 'vue-apollo';
Vue.config.productionTip = false;
// Hasura GraphQL Api adresimiz
const hasuraLink = new HttpLink({ uri: 'https://basketin-cepte.herokuapp.com/v1alpha1/graphql' });
/*
Servis iletişimini sağlayan nesne
GraphQL istemcileri veriyi genellikle cache'de tutar.
Tarayıcı ilk olarak cache'ten okuma yapar.
Performans ve network trafiğini azaltmış oluruz bu şekilde.
*/
const apolloClient = new ApolloClient({
link: hasuraLink, // Kullanacağı servis adresini veriyoruz
connectToDevTools: true, // Chrome'da dev tools üzerinde Apollo tab'ının çıkmasını sağlar. Debug işlerimiz kolaylaşır
cache: new InMemoryCache() // ApolloClient'ın varsayılan Cache uyarlaması için InMemoryCache kullanılıyor.
});
// Vue ortamının GraphQL ile entegre edebilmek için VueApollo kütüphanesini entegre ediyoruz. (https://akryum.github.io/vue-apollo/)
Vue.use(VueApollo);
/*
Vue tarafında GraphQL sorguları oluşturabilmek ve veri girişleri(mutations)
yapabilmek için ApolloProvider örneği kullanmamız gerekiyor.
VueApollo'den üretilen bu nesnenin Hasura tarafına işlemleri commit
edebilmesi içinse yukarıdaki apolloClient'ı parametre olarak atıyoruz
*/
const apolloProvider = new VueApollo({
defaultClient: apolloClient
});
new Vue({
apolloProvider,// Vue uygulamamızın ApolloProvider'ı kullanabilmesi için eklendi
render: h => h(App),
}).$mount('#app');</pre>
<blockquote>
<p>TODO <em>(Benim tembelliğimden size düşen)</em></p>
<p>Bu servisi JWT Authentication bünyesine almak lazım. İşte size güzel bir araştırma konusu. Başlangıç noktası olarak Auth0'ın <a href="https://auth0.com/docs/quickstart/spa/vuejs" target="_blank">şu dokümanına</a> bakılabilir. Ben şu an için sadece HASURA_GRAPHQL_ADMIN_SECRET kullanarak servis adresine erişimi kısıtlamış durumdayım. Zaten büyük ihtimalle yazıyı okuduğunuzda onun yerinde yeller estiğine şahit olacaksınız.</p>
</blockquote>
<h2>Çalışma Zamanı</h2>
<p>Hasura servisimiz ve istemci taraftaki uygulamamız hazır. Artık çalışma zamanına geçip sonuçları irdeleyebiliriz. Programı başlatmak için</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">npm run serve</pre>
<p>terminal komutunu vermemiz yeterli. Sonrasında http://localhost:8080 adresine giderek ana sayfaya ulaşabiliriz. Aynen aşağıdakine benzer bir görüntü elde etmemiz gerekiyor.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_24.png" alt="" /></p>
<p>Yeni ürün ekleme bileşeni konulduktan sonrasına ait örnek bir ekran görüntüsünü de buraya iliştirelim.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_25.png" alt="" /></p>
<p>Hatta yeni bir forma eklediğimizde gönderilen Graphql Mutation sorgusundan dönen değer, F12 sonrası Network sekmesinden yakalayabiliriz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/09/37/credit_26.png" alt="" /></p>
<blockquote>
<p>throw new UnDoneException("Yeni ürün ekleme sayfasında kategori seçiminde combobox kullanımı yapılmalı");</p>
</blockquote>
<h2>Ben Neler Öğrendim?</h2>
<p>Doğruyu söylemek gerekirse bu çalışma benim için oldukça keyifliydi. Heroku platformunu oldukça beğeniyorum. Şirkette Vue tabanlı ürünlerimiz var ama onlar üzerinden çok iyi değilim. Dolayısıyla Vue tarafında bir şeyler yapmış olmak beni mutlu ediyor. Peki bu çalışma kapsamında neler mi öğrendim. İşte listem...</p>
<ul>
<li>Heroku'da Docker Container içerisinde çalışan ve PostgreSQL verilerini GraphQL ile sorgulanabilir olarak sunan Hasura isimli bir motor olduğunu</li>
<li>Hasura arabirimden tablolar arası ilişkileri nasıl kuracağımı</li>
<li>Bir kaç basit GraphQL sorgusunu<em>(sorgularda sayfalama yapmak, ilişkili veri getirmek, where koşulları kullanmak)</em></li>
<li>Vue tarafında GraphQL sorgularının nasıl gönderilebileceğini</li>
<li>Component içinde Component kullanımlarını<em>(App bileşeni dışında product-list içinde product-item kullanımı)</em></li>
<li>Temel component tasarlama adımlarını</li>
<li>Vue tarafından bir Mutation sorgusunun nasıl gönderilebileceğini ve schema veri tiplerine dikkat etmem gerektiğini</li>
</ul>
<p>Böylece geldik bir <a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">cumartesi gecesi derlemesi</a>nin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2019-11-06T10:30:00+00:00hasuravuegraphqljavascriptapihtmlmutationcomponentherokupostgresqldockercontainerbootstrapgrapiqlcrudbsenyurtAPI'ler için türlendirilmiş(typed) sorgulama dillerinden birisi olarak öne çıkan GraphQL'e bir süredir uğramıyordum. Daha doğrusu GraphQL sorgusu çalıştırılabilecek şekilde API servis hazırlıklarını yapmaya üşeniyordum. Bu nedenle işi kolaylaştıran ve Heroku üzerinden sunulan Hasura GraphQL Engine hizmetine bakmaya karar verdim. Hasura, veriyi PostgreSQL kullanarak saklıyor ve ayrıca API'yi bir Docker Container içerisinden sunuyor. Amacım Hasura tarafında hazırlayacağım iki kobay veri setini, Vue.js tabanlı bir istemcisinden tüketmek. Basitçe listeleme yapıp, veri ekleme işini yapabilsem başlangıç aşamasında yeterli olur. İşe Hasura servisini hazırlayarak başlamak lazım.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=15b6174f-8b6f-47dd-a0e8-9b975850f7f00https://buraksenyurt.com/trackback.axd?id=15b6174f-8b6f-47dd-a0e8-9b975850f7f0https://buraksenyurt.com/post/hasura-graphql-engine-ile-gelistirilmis-bir-api-servisini-vue-js-ile-kullanmak#commenthttps://buraksenyurt.com/syndication.axd?post=15b6174f-8b6f-47dd-a0e8-9b975850f7f0https://buraksenyurt.com/post/mongodb-express-vue-ve-node-birlikteligiMongoDb,Express,Vue ve Node Birlikteliği2019-07-19T05:47:00+00:00bsenyurt<p><img style="float: right;" src="https://buraksenyurt.com/image.axd?picture=/2019/06/13/cobolll.png" alt="" />Aranızdan kaç kişi akıllı telefonundaki herhangi bir arkadaşının numarasını ezbere söyleyebilir? Eminim bazılarımız bizi dokuz ay karnında taşıyan annesinin telefonunu dahi hatırlamıyordur. Peki ya birlikte sıklıkla vakit geçirdiğiniz ama çok da yakın çevrenizden olmayan kankanızın doğum günü ne zaman? Teknolojik cihazlarınızdaki hatırlatıcılar olmadığında kaç arkadaşınızın doğum gününü unutacaksınız hiç düşündünüz mü?</p>
<p>İletişim bilgisi ve doğum günleri bizi yakın çevremize bağlayan veya uzağı yakın eden unsurlar arasında yer alıyor. Hatırlanmak güzel olduğu kadar hatırlamak da gerekiyor. Ortaokul sıralarında kullandığım bir fihrist defterim vardı. İçinde yakın arkadaşlarımın ev telefonları ve doğum günü bilgileri yazardı. Pek tabii çok sık iletişimde olduğum sıra arkadaşım sevgili Burak Gürkan gibi dostlarımı aramak için o deftere ihtiyacım yoktu. Neredeyse her gün telefonla konuştuğumuz için numarayı ezberlemiştim.</p>
<p>Aradan yıllar geçti ve Yıldız Teknik Üniversitesi Matematik Mühendisliği bölümünü kazandım. Okulun ikinci yılındaki bilgisayar programlama dersinde Cobol görüyorduk ve dönem sonuna doğru hocamızla birlikte yaptığımız o uzun metrajlı çalışmanın konusu fihrist defteriydi<em>(O yıl Cobol ile ilk ve son karşılaşmam olur diye düşünsem de hayatımın ilerleyen yıllarında iki kez karşıma çıkarak bende hoş anıların oluşmasına neden olacaktı)</em> Bu kez arkadaşlarımızın iletişim bilgilerini ve doğum günlerini tutmak için 3.5 inçlik floppy diskten, 1ler ve 0lardan yararlanacaktık. Her ne kadar o zamanlar için heyecan verici bugün içinse çok sıradan bir örnek olsa da benim için geçmişe yapılan manevi bir yolculuk. Dolayısıyla zaman zaman bu örnek konsepti bir şeyleri öğrenmeye çalışırken kullanıyorum. İşte sıradaki <a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">saturday-night-works birinci faz</a> derlememizin konusu da bir fihrist. Gelin hiç vakit kaybetmeden notlarımızı toparlamaya başlayalım.</p>
<p>Amacım başlıkta geçen enstrümanları kullanarak Web API tabanlı basit bir web uygulaması geliştirmekti. Veriyi tutmak için MongoDB'yi, sunucu tarafı için Node.js'i, Web Framework amacıyla express'i ve önyüz geliştirmesinde de Vue'yu kullanmak istemiştim. Kobay olarakta doksanlı yıllardan aklıma gelen ve Cobol öğretirlerken gösterdikleri Fihrist örneğini seçtim<em>(O vakitler sanırım hepimiz öğrendiğimiz dillerle arkadaşlarımızın telefon numaralarına yer verdiğimiz bir fihrist uygulaması yazmışızdır)</em> Özellikle Vue tarafında bileşen<em>(component)</em> geliştirmenin nasıl yapılabileceğini, bunlar arasındaki haberleşmenin nasıl tesis edileceğini merak ediyordum. İşin içerisine WebPack de girince güzel bir çalışma alanı oluştu diyebilirim.</p>
<h2>Projenin İskeleti</h2>
<p>Projenin genel iskelet yapısı ve kullanacağımız dosyaları aşağıda görülen hiyerarşide oluşturabiliriz.</p>
<pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false">Fihrist/
|----- app/
|----- config.js (Mongo bağlantısı gibi ortam parametrelerini tuttuğumuz konfigurasyon modülü)
|----- Routers.js (HTTP Get,Post,Put,Delete operasyonlarını üstlenen WebAPI tarafı)
|----- Contact.js (mongodb tarafı için kullanılan entity modeli)
|----- public/
|----- src/
|------------ bus.js (vue component'leri arasındaki iletişimi sağlayan event bus dosyası)
|------------ main.js (vue tarafının giriş noktası)
|------------ vue.js (vue npm paketi yüklendikten sonra dist klasöründen alınıp buraya kopyalanmıştır)
|------------ components/ (vue componentlerini tuttuğumuz yer)
|----------------- createContact.vue (yeni bir bağlantı eklemek için kullanılan bileşen)
|----------------- contacts.vue (tüm kontak listesini gösteren bileşen)
|----------------- app.vue (ana vue bileşeni)
|----- index.html (kullanıcının etkileşimde olacağı ana sayfa)
|----- server.js (sunucu tarafı)
|----- webpack.config.js (webpack build işleminin kullandığı konfigurasyon dosyası)</pre>
<p>app klasöründe model sınıf, yönlendirme paketi ve bir konfigurasyon dosyası yer alıyor. public klasöründe HTML ve gerekirse CSS, image gibi öğlere ve vue uygulamasının kendisine yer veriliyor. server.js tahmin edileceği üzere node server rolünü üstleniyor. </p>
<h2>Gerekli Kurulumlar</h2>
<p>Sistemde node, npm ve mongodb'nin yüklü olduğunu varsayıyoruz. Buna göre kök klasörde,</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">npm init</pre>
<p>ile başlangıcı yapıp gerekli paket kurulumlarını tamamlayabiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">npm install body-parser express mongoose morgan</pre>
<p>JSON bazlı servise ait mesaj gövdelerini kolayca parse etmek için body-parser, HTTP sunucusu ve servis taleplerinin karşılanması için express, mongodb veri tabanı ile konuşmak için mongoose, mongo loglarını console tarafından izleyebilmek için morgan paketlerini kullanıyoruz. Buna göre server.js dosyasının içeriğini aşağıdaki gibi kodlayabiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">var express = require('express')
var morgan = require('morgan')
var path = require('path')
var app = express()
var mongoose = require('mongoose')
var bodyParser = require('body-parser')
var config = require('./app/config')
var router = require('./app/router')
mongoose.connect(config.conn, { useNewUrlParser: true }) // konfigurasyon dosyasındaki bilgi kullanılarak mongoDb bağlantısı tesis edilir
// static dosyaların public klasöründen karşılanacağı belirtilir
app.use(express.static(path.join(__dirname, '/public')))
// Middleware katmanına morgan'ı enjekte ederek loglamayı etkinleştirdik
app.use(morgan('dev'))
// İstemci taleplerinden gelecek Body içeriklerini JSON formatından kolayca ele almak için
// Middleware katmanına body-parser modülünü ekledik
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
var port = config.default_port || 8080 // port bilgisi belirlenir. config'de varsa 5003 yoksa 8080
app.listen(port)
app.use('/api', router) // /api adresine gelecek taleplerin router modülü tarafından karşılanacağı belirtilir.
app.get('/', function (req, res, next) {
res.sendFile('./public/index.html') // eğer / adresine talep gelirse (yani http://localhost:5003/ şeklinde) index.html sayfasına yönlendiriyoruz
})
console.log('Sunucu hazır ve dinlemede')</pre>
<p>Sunucu tarafında kullandığımız yardımcı modüllerimiz olacak. Web API tarafı için router.js'i ve MongoDb veri tabanı bağlantısını konfigurasyon dosyasından beslememizi sağlayan config.js ki içeriğini aşağıdaki gibi oluşturabiliriz.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false">// genel konfigurasyon ayarlarımız
// veritabanı bağlantısı, sunucu için varsayılan port bilgisi vs
module.exports={
conn:'mongodb://localhost:27017/fihristim',
default_port:5003
}</pre>
<p>Web API görevini üstlenen router.js'in temel görevi CRUD operasyonları için HTTP desteği sunmak<em>(Read için Get, Create için Post vb)</em> Her ne kadar ilişkisel bir veri tabanı kullanmıyor olsak da, Mongo tarafındaki koleksiyon için bir şema bilgisi tanımlamamız gerekiyor. Yani bir model oluşturmalıyız. Bunun için contact isimli modülü oluşturabiliriz. Arkadaşımızın tam adını, telefon numarasını, yaşadığı yeri ve doğum tarihi bilgilerini tutan bu modelde sadece String ve Date veri türlerine yer vermiş olsak da siz veri farklı tiplerle yapıyı pekala zenginleştirebilirsiniz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">// MongoDb'de koleksiyonunun karşılığı olan model tanımı
var mongoose = require('mongoose')
// contact isimli bir şemamız var
// örnek olması açısından bir kaç özellik içeriyor
var contact = new mongoose.Schema({
fullname: { type: String },
phoneNumber: { type: String },
location: { type: String },
birtdate: { type: Date }
},
{
collection: 'contacts' // kontaklarımızı tuttuğumuz koleksiyon
}
)
module.exports = mongoose.model('Contact', contact)</pre>
<p>Bu şemayı kullanan ve esas itibariyle CRUD operasyonlarının karşılığı olan servis taleplerini ele alan router dosyasının içeriğini de aşağıdaki gibi geliştirerek devam edebiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">// Web API Router sınıfımız
// gerekli modülleri tanımlıyoruz
var express = require('express')
var operator = express.Router()
var contact = require('./contact')
// HTTP Post ile yeni bir contact eklenmesini sağlıyoruz
operator.route('/').post(function (req, res) {
// request body'den gelen değerlere göre contact oluşturuluyor
contact.create({
fullname: req.body.fullname,
phoneNumber: req.body.phoneNumber,
location: req.body.location,
birtdate: req.body.birtdate
}, function (e, c) { //callback fonksiyonu
if (e) { //hata oluşmuşsa HTTP 400 döndük
res.status(400).send('kayıt işlemi başarısız')
}
res.status(200).json(c) // Hata yoksa HTTP 200 Ok dönüyor ve cevabın içine oluşturulan contact nesnesini gömüyoruz
}
)
})
// HTTP Get talebi için tüm kontakların listesini dönüyoruz
operator.route('/').get(function (req, res, next) {
contact.find(function (e, contacts) {
if (e) { //hata varsa sonraki fonksiyona bunu yollar
return next(new Error(e))
}
res.json(contacts) // hata yoksa tüm kontaları json serileştirip döner
})
})
// Belli bir ID'ye ait kontak bilgisini döndürür
// HTTP Get ile çalışır
// Querystring'teki id kullanılır
operator.route('/:id').get(function (req, res, next) {
var id = req.params.id //id parametresinin değeri alınır
contact.findById(id, function (e, c) {
if (e) //hata varsa kayıt bulunanamış diyebiliriz
{
return next(new Error('Bu ID için bir kontak bilgisi mevcut değil'))
}
res.json(c)
})
})
// ID bazlı kontak silmek için çalışan fonksiyon
// HTTP Delete kullanılır
operator.route('/:id').delete(function (req, res, next) {
var id = req.params.id
contact.findByIdAndRemove(id, function (e, c) {
if (e) {
return next(new Error('Bu ID için bir kontak bulunamadığından silme işlemi yapılamadı'))
}
res.json('Başarılı bir şekilde silindi')
})
})
// Güncelleme işlemi
// HTTP Put kullanılır
operator.route('/').put(function (req, res, next) {
var id = req.body.id
// önce id'den contact bulunur
contact.findById(id, function (e, c) {
if (e) {
return next(new Error('Güncellenme için bir kayıt bulunamadı'))
} else { //bulunduysa özellikler body'den gelenler ile değiştirilir
c.fullname = req.body.fullname ? req.body.fullname : c.fullname
c.phoneNumber = req.body.phoneNumber ? req.body.phoneNumber : c.phoneNumber
c.location = req.body.location ? req.body.location : c.location
c.birtdate = req.body.birtdate ? req.body.birtdate : c.birtdate
// contact yeni haliyle kayıt edilir
c.save()
res.status(200).json(c)
}
})
})
module.exports = operator</pre>
<p>server.js ve App klasörü içindeki dosyalarımız hazır. An itibariyle sunucu tarafını çalıştırabilir ve çeşitli servis çağrıları gereçekleştirerek Mongo üzerinde CRUD operasyonlarını deneyimleyebiliriz. Sunucu tarafını başlatmak için terminalden</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">npm start</pre>
<p>komutunu vermek yeterli. Tabii bu aşamada MongoDb'nin de çalışır olduğundan emin olmak lazım. mongod ile mongodb servisini başlatabiliriz. Sonrasında veri tabanı üzerindeki operasyonlar için mongo komutunu kullanarak arabirim haberleşmesini de açabiliriz. Eğer mongod ile servis başarılı bir şekilde çalışırsa 27017 port veri tabanı haberleşmesi için aktif hale gelecektir. Sonrasında örneğin db komutunu kullanılabilir ve örneğin veri tabanlarını listeyebiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">mongod
mongo
db</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/06/13/credit_1.png" alt="" /></p>
<h2>Servis Testleri</h2>
<p>Servis tarafının işlerliğini kontrol etmek için curl aracıyla aşağıdaki denemeler yapılabilir. Ben denemeler sırasında en yakın arkadaşlarımdan dördünü ekledim. Sonrasında listeleme, belli bir id'ye bağlı kişi çekme, bilgi güncelleme ve silme operasyonlarını icra ettim. </p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">curl -H "Content-Type: application/json" -X POST -d '{"fullname":"M.J.","phoneNumber":"555 55 23","location":"chicago","birtdate":"1963-05-18T16:00:00Z"}' http://localhost:5003/api
curl -H "Content-Type: application/json" -X POST -d '{"fullname":"Çarls Barkli","phoneNumber":"555 55 34","location":"phoneix","birtdate":"1963-05-18T16:00:00Z"}' http://localhost:5003/api
curl -H "Content-Type: application/json" -X POST -d '{"fullname":"meycik cansın","phoneNumber":"555 55 32","location":"los angles","birtdate":"1959-05-18T16:00:00Z"}' http://localhost:5003/api
curl -H "Content-Type: application/json" -X POST -d '{"fullname":"leri börd","phoneNumber":"555 55 33","location":"boston","birtdate":"1956-05-18T16:00:00Z"}' http://localhost:5003/api
curl http://localhost:5003/api
curl http://localhost:5003/api/5c29222522433f0234e71e1b
curl -H "Content-Type: application/json" -X PUT -d '{"id":"5c29222522433f0234e71e1b","fullname":"maykıl cordın"}' http://localhost:5003/api
curl -X DELETE http://localhost:5003/api/5c29222522433f0234e71e1b</pre>
<p>Belli dokümanlar için kullanılan ve MongoDb tarafından otomatik olarak üretilen ID değerleri elbette siz kendi denemelerinizi yaparken farklılıklar gösterecektir.</p>
<h2>Front-End Tarafı</h2>
<p>Servis tarafı hazır. Elimizde veri kaynağı ile haberleşen ve temel işlemleri gerçekleştiren bir sunucu mevcut. Şimdi bu servisle konuşan arayüz uygulamasını tasarlamaya başlayalım. Tüm front-end enstrümanları public klasörü altında konuşlanmış durumdadır. Javascript ve css öğelerini tek bir paket haline getirmek için WebPack'ten faydalanacağız. Vue tarafındaki HTTP çağrıları için istemci olarak axios paketini kullanacağız. Gereken paket kurulumları için terminalden şöyle ilerleyebiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">npm install babel-core babel-loader@7 babel-preset-env babel-preset-stage-3 css-loader vue-loader vue-template-compiler webpack webpack-dev-server bootstrap</pre>
<p>Vue tarafındaki ana bileşenimiz app.vue dosyasında bulunuyor. Kendi içinde iki alt bileşene sahip. Bir kontak eklemek için kullanacağımız newContact.vue ve listeleme için ele alacağımız contacts.vue. Bu bileşenleri aşağıdaki gibi kodlayabiliriz.</p>
<p>newContact.vue</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><template>
<div>
<h1>Yeni Bağlantı</h1>
<form>
<div class="form-group">
<label>Fullname</label>
<input
type="text"
class="form-control"
aria-describedby="inputGroup-sizing-default"
placeholder="nasıl isimlendirirsin?"
v-model="contact.fullname"
>
</div>
<div class="form-group">
<label>Phone</label>
<input
type="text"
class="form-control"
aria-describedby="inputGroup-sizing-default"
placeholder="nereden ulaşırsın?"
v-model="contact.phoneNumber"
>
</div>
<div class="form-group">
<label>Location</label>
<input
type="text"
class="form-control"
aria-describedby="inputGroup-sizing-default"
placeholder="nerede yaşıyor?"
v-model="contact.location"
>
</div>
<div class="form-group">
<label>Birthdate</label>
<input
type="text"
class="form-control"
aria-describedby="inputGroup-sizing-default"
placeholder="1976-04-12T11:35:00Z"
v-model="contact.birtDate"
>
</div>
<div class="form-group">
<button type="button" class="btn btn-primary" @click="createContact($event)">Kaydet</button>
</div>
</form>
</div>
</template>
<script>
import axios from "axios"; // API servis haberleşmesi için
import bus from "./../bus.js"; // bileşenler arası haberleşme için
// HTML elementlerindeki input kontrollerinde dikkat edileceği üzere v-model attribute'ları kullanıldı. Bunlar modelimizin özellikleri.
export default {
data() {
return {
contact: {
fullname: "",
phoneNumber: "",
location: "",
birtDate: ""
}
};
},
methods: {
createContact(event) {
//Button'un @click niteliğinde yüklenen olay metodu
if (event) event.preventDefault();
let url = "http://localhost:5003/api";
let param = {
//parametre değerleri input kontrollerinden geliyor
fullname: this.contact.fullname,
phoneNumber: this.contact.phoneNumber,
location: this.contact.location,
birtDate: this.contact.birtDate
};
axios
.post(url, param) //HTTP Post çağrısını gönderdik
.then(response => {
console.log(response); // tarayıcının developer tool kısmından log takibi için. Canlı ortamda kullanmaya gerek yok.
this.clear();
this.refresh(); // yeni bir bağlantı oluşturlduğunda refresh metodu çağırılır
})
.catch(error => {
console.log(error);
});
},
clear() {
this.contact.fullname = "";
this.contact.phoneNumber = "";
this.contact.location = "";
this.contact.birtDate = "";
},
refresh() {
bus.$emit("refresh"); // metod evenbus'a bir olay fırlatır. refresh isminde. bunu diğer bileşende yakalayarak bağlantılar listesini anında günelleyebiliriz
}
}
};
</script></pre>
<p>contacts.vue bileşenimiz</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><template>
<div>
<div class="col-md-12" v-show="contactList.length>0">
<h3>Tüm bağlantılarım</h3>
<div class="row mrb-10" v-for="contact in contactList" :key="contact.id">
<div class="card bg-light border-dark mb-3" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">{{ contact.fullname }}</h5>
<p class="card-text">{{ contact.phoneNumber }}</p>
<p class="card-text">{{ contact.location }}</p>
<p class="cart-text">{{ contact.birtdate }}</p>
<span v-on:click="deleteContact(contact._id)" class="btn btn-primary">Sil</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import bus from "./../bus.js";
export default {
data() {
return {
contactList: [] //modelimizin verisini içerecek array elemanı
};
},
created: function() {
// başlangıçta çalışacak fonksiyonumuzda iki işlem yapılıyor
this.getAllContacts(); // tüm bağlantıları al
this.listenToBus(); // ve diğer bileşenden yeni eklenecek bağlantıları alabilmek için eventBus'ı dinlemeye başla
},
methods: {
getAllContacts() {
let uri = "http://localhost:5003/api"; // klasik axios ile web api'mize HTTP Get talebi gönderdik
axios.get(uri).then(response => {
this.contactList = response.data; //dönen veriyi contactList dizisine aldık
console.log(this.contactList);
});
},
deleteContact(id) {
// bir arkadaşımızı silmek istediğimizde
let uri = "http://localhost:5003/api/" + id;
axios.delete(uri); // HTTP Delete talebini gönderiyoruz. id parametresi Mongo'nun ürettiği Guid
this.getAllContacts(); // listemizi tazeleyelim
},
listenToBus() {
bus.$on("refresh", $event => {
this.getAllContacts(); // Diğer bileşen tarafından yeni bir bağlantı eklenirse dinlediğimiz refresh isimli hattan bunu yakalayabileceğiz. Bu uyarı sonrası bağlantı listesini tekrar çekiyoruz
});
}
}
};
</script></pre>
<blockquote>
<p>Özellikle yukarıdaki iki bileşenin kullandığı bus isimli modüle dikkat etmek lazım. Vue tarafında bileşenler arasında olay bazlı çalışan bir eventbus modeli ile iletişim sağlanabilir. Bu sayede bir bileşende yapılan değişiklikleri başka birisine göndermek mümkündür. Örneğimizdeki newContact bileşeninde refresh isimli bir olay tetiklenir <em>(Yeni bir kontak eklendiğinde çalıştırıyoruz)</em> Contacts bileşeni de refresh isimli olayı dinlemektedir. Dolayısıyla yeni bir kontak bilgisi eklediğimizde diğer bileşen otomatik olarak güncel kontak listesini çekecektir.</p>
</blockquote>
<p>ve son olarak app.vue isimli ana bileşenimiz.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><template>
<div id="app">
<div class="container">
<div class="row col-md-6 offset-md-3">
<create-contact></create-contact>
<!-- createContact bileşeni buraya yerleşecek diğeri de aşağıya -->
<contacts></contacts>
</div>
</div>
</div>
</template>
<script>
import createContact from "./newContact.vue"; // yeni bağlantı eklediğimiz bileşeni aldık
import contacts from "./contacts.vue"; // contact bileşenini aldık
export default {
name: "app",
data() {
return {};
},
components: { createContact, contacts } // bileşenlerimizi tanıttık
};
</script></pre>
<p>Frontend tarafı geliştmeleri bittikten sonra bir build işlemi ile mypackage.js dosyasını oluşturacağız. Bu içerik Vue bileşenlerinin de sunulacağı index.html dosyasında referanslanacak. Çok sade bir HTML içeriği elde edeceğimizi ifade edebilirim :) Tabii burada build süreci için gerekli bir webpack.config.js dosyasına ihtiyaç var. İlgili dosyayı aşağıdaki gibi kodlayabiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
entry: './public/src/main.js',
output: {
filename: './public/build/mypackage.js'
},
resolve: {
alias: {
vue: './vue.js'
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}, {
test: /\.vue$/,
loader: 'vue-loader',
exclude: /node_modules/,
options: {
loaders: {
}
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
},
plugins: [
new VueLoaderPlugin()
],
devServer: {
port: 3000
}
}</pre>
<p>Build sonrası oluşan paket için bir takım bildirimlere yer verilmektedir. Giriş noktası olarak src klasöründeki main.js tanımlanmıştır. Build sonrası çıktı output sekmesinde belirtilen ortama yapılacaktır. Devam eden kısımlarda css, vue vs js formatlı dosyalar için henüz ne anlama geldiklerini öğrenemediğim kural setleri vardır. Dosya tamamlandıktan sonra build işlemine geçilebilir. Aşağıdaki terminal komutunu bu amaçla kullanabiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">npm run build</pre>
<p>Eğer sorunsuz bir şekilde<em>(sorunsuz diyorum çünkü webpack.config.js dosyasını ayarlarken epey problem yaşadım) </em>build işlemi gerçekleşirse dist/public/build/mypackage.js şeklinde bir dosya oluşur. Bu bohçayı index.html public klasörüne alıp aşağıdaki gibi kullanıma açabiliriz.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Benim en iyi arkadaşlarım</title>
</head>
<body>
<app></app>
<script src="./build/mypackage.js"></script>
</body>
</html></pre>
<p>Hepsi bu kadar ;) Yazdığımız uygulamayı test etmek için node ve mongodb sunucularını çalıştırmamız ve http://localhost:5003/ adresine gitmemiz yeterli. Malum burası varsayılan olarak index sayfasına yönlendirilmekte<em>(Nereden yapıldığını hatırlıyor musunuz?)</em> index.html içinde Vue kodlarımızı paketlediğimiz mypackage.js dosyası referans edildiğinden ilgili bileşenler de buraya render edilecek.</p>
<blockquote>
<p>Build sonrası bileşenlerde bir değişiklik yapılırsa tekrardan build işleminin çalıştırılması ve bundle paketinin kullanıma alınması gerekir.</p>
</blockquote>
<p>Ekleme işlemi ile ilgili ilk test sonucu aşağıdaki gibidir.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/06/13/credit_2.png" alt="" /></p>
<p>Listeleme ve listenen bağlantıları silme işini contacts.vue isimli bileşen üstlenmekte. Buda app.vue içerisinde tanımlanıp sayfaya yerleştiriliyor. contacts.vue içerisinde bootstrap card stillerini kullanmayı tercih ettim. Çok berbat bir tasarım olmadı ama çok iyi de olmadı. Fonksiyonel olarak bağlantıları listeletebiliyor, silme ve yenilerini ekleme işlemlerini gerçekleştirebiliyoruz.</p>
<blockquote>
<p>Update işlemi içinde buraya bir şeyler eklemek lazım. Mesela bir link'le farklı bir adrese yönlendirilme ve onun üstünden güncelleme işlemlerinin yapılması sağlanabilir. Bu kutsal görevi...... :D</p>
</blockquote>
<p>İşte örnek bir ekran görüntüsü daha...</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/06/13/credit_3.png" alt="" /></p>
<h2>Ben Neler Öğrendim?</h2>
<p>Olayın başlangıç noktası olan index.html son derece sadedir. İçinde bootstrap ve vue bileşenleri gibi gerekli diğer kütüphaneleri barındırmaktadır. Bu sadeliği webpack sağlıyor ama bu ifadem mutlaka webpack'i kullanın anlamına gelmemeli. Farklı alternatif bundler araçları da mevcut. Benim bu çalışma sonrası öğrendiklerim ise şöyle;</p>
<ul>
<li>Vue'da component nasıl geliştirilir</li>
<li>template üzerinde model kullanımı nasıldır</li>
<li>axios ile HTTP Post,Get,Put gibi metodlar nasıl çağrılır</li>
<li>webpack.config dosyası nasıl hazırlanır</li>
<li>bir bileşenden eventbus'a bildirim nasıl yapılır ve diğer bileşenlerden bu değişiklik nasıl yakalanır</li>
<li>Bootstrap Card'ları nasıl kullanılır</li>
</ul>
<p>Böylece geldik bir <a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">cumartesi gecesi çalışması</a>na ait derlemenin daha sonuna. <a href="https://github.com/buraksenyurt/saturday-night-works/tree/master/No%2013%20-%20MEVN%20Sample" target="_blank">13 numaralı örnek</a>le geçmişe bir yolculuk daha yaptım. Tekrardan okuyunca unuttuğum bir çok noktayı yeniden hatırladığım bir yazı olduğu için kendi adıma kardayım. Umarım sizler için de faydalı olmuştur. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2019-07-19T05:47:00+00:00vue.jsmongodbexpressnodenode.jsjavascriptnpmcurlwebpackresthttpcrudmongoosenosqlhtmlbootstrapeventbusvuebsenyurtAmacım bu 4 enstrümanı kullanarak Web API tabanlı çalışan basit bir web uygulaması geliştirmek. Veriyi tutmak için MongoDB'yi, sunucu tarafı için Node.js'i, Web Framework amacıyla Express'i ve önyüz geliştirmesinde de Vue.js'i kullanmak istedim. Kobay olarakta 90lardan aklıma gelen ve Cobol öğretirlerken gösterdikleri Fihrist örneğini seçtim. Ayrıca WebPack'i de işin içerisine katıp paketleme operasyonunu da deneyimlemeye çalıştım.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=756133be-0c50-4fa6-8165-6b0a2c4160bd2https://buraksenyurt.com/trackback.axd?id=756133be-0c50-4fa6-8165-6b0a2c4160bdhttps://buraksenyurt.com/post/mongodb-express-vue-ve-node-birlikteligi#commenthttps://buraksenyurt.com/syndication.axd?post=756133be-0c50-4fa6-8165-6b0a2c4160bdhttps://buraksenyurt.com/post/vue-ve-nw-js-ile-desktop-uygulamasi-gelistirmekVue ve NW.js ile Desktop Uygulaması Geliştirmek2019-06-14T13:00:00+00:00bsenyurt<p><img style="float: right;" src="https://buraksenyurt.com/image.axd?picture=/2019/05/16/perfectstr.png" alt="" />Geçen gün fark ettim ki yaş ilerleyince blogumdaki yazıların girişinde kullanabileceğim malzeme sayısı da artmış. Söz gelimi şu anda lise son yıllarıma yani seksenlerin sonu doksanların başına doğru gitmiş durumdayım. O dönemlerde kısa Amerikan dizileri popüler. Hatta Arjantin menşeeli diziler de çok yaygın. Sanıyorum Mariana isimli popüler bir dizi vardı. Kısa boylu, siyah kıvırcık saçlı, buğday tenli ve hayatı acılar içinde geçen bir Latin kadının hikayesiydi. Lakin ben hayatı toz pembe görmemize vesile olan komedileri tercih ediyordum. Hatta en çok sevdiğim komedi dizisi <a href="https://www.imdb.com/title/tt0090501/?ref_=nv_sr_2?ref_=nv_sr_2" target="_blank">Perfect Strangers</a>'dı.</p>
<p>Mipos isimli Yunan köyünden Chicago'daki kuzeni Larry Appleton'ın yanına yerleşip "Komik olma kuzen" repliği ile zihnime kazınan Balki Bartokomous bizleri epeyce güldürürdü. Aradan çeyrek asır geçmiş olsa da aptal kutunun bizleri ekrana bağlayan bazı alışkanlıkları değişmiyor. Platformlar belki ama yine komedi dizileri, yine Arjantin dizileri ve yine aklımıza kazınan Balki'ler var. <a href="https://github.com/buraksenyurt/saturday-night-works/tree/master/No%2016%20-%20Build%20Desktop%20App%20with%20Vue%20and%20NWjs" target="_blank">Saturday-Night-Works'ün 16 numaralı çalışması</a>na konu olan Big Bang Theory'de işte bana bu çağrışımları yapmış durumda. Öyleyse gelin başlayalım.</p>
<p>Daha önceden <a href="https://github.com/buraksenyurt/electron" target="_blank">Electron ile cross platform desktop uygulamaları</a>nın geliştirilmesi üzerine çalışmıştım<em>(github repo istatistiklerine göre kimsenin ilgisini çekmemişti ama malum çok eski bir desktop programıcısı olduğumdan ilgilenmiştim)</em> Bu kez eskiden node-webkit olarak bilinen <a href="https://nwjs.io/" target="_blank">NW.js kullanarak</a> WestWorld üzerinde desktop uygulaması geliştirmek istedim. NW.js cephesinde de aynen Electron'da olduğu gibi Chromium, Node.js, HTML, CSS ve javascript kullanılmakta. Lakin ufak tefek farklılıklar var. Electron'da entry point yeri Javascript script'i iken NW.js tarafında script haricinde bir web sayfası da giriş noktası olabiliyor. Build süreçlerinde de bir takım farklılıklar var.</p>
<p>Peki bu çalışma kapsamında ne yapacağız? Uygulama çok basit bir arayüze sahip olacak. Ekrandaki metin kutusuna bir isim girilecek ve Big Bang Theory'nin ilgili bölümüne ait bazı bilgiler ekrana bastırılacak<em>(Akıllı bir arama ekranı değil çok şey beklemeyin)</em> Bölüm bilgisini ise bigbangapi isimli ve .net core ile yazılmış bir web api servisi sağlayacak<em>.</em></p>
<h2>Başlangıç</h2>
<p>WestWorld'de<em>(Ubuntu 18.04 64bit)</em> bu örnek için Vue CLI'a<em>(Vue'nun Command Language Interface aracı olarak düşünebiliriz)</em> ihtiyaç var. Önce versiyonu kontrol edip yoksa yüklemek lazım. Ayrıca projeyi oluşturduktan sonra NW paketini de eklemek gerekiyor. axios'u servis haberleşmesi için kullanacağız. Bunun için terminalden aşağıdaki adımlarla ilerleyebiliriz. vue create ile başlayan satır bbtheory isimli hazır bir Vue uygulaması inşa edecek. npm install satırlarında da bu uygulama için gerekli paketlerin yüklenmesi sağlanıyor. Nw sdk ve axios bu anlamda önemli.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">vue --version
sudo npm install -g @vue/cli
vue create bbtheory
cd bbtheory
sudo npm install --save-dev nwjs-builder-phoenix nw@sdk
sudo npm install axios</pre>
<blockquote>
<p>Vue projesi varsayılan kurulum ayarları ile oluşturulmuştur.</p>
</blockquote>
<h2>Kod Tarafı</h2>
<p>Gelelim kodlama tarafına. Uygulamanın masaüstü arayüzü olan App bileşeni app.vue dosyasında kodlanıyor. Bu dosyayı aşağıdaki gibi değiştirerek ilerleyebiliriz. Sonuçta HTML tabanlı bir ortam var. Elbette Vue'ya özgü bir sentaks da söz konusu. Söz gelimi bileşendeki bir kontrolü model tarafına bağlamak için v-model direktifinden yararlanılıyor. Bir section elementinin görünürlüğünü koşullandıracaksak v-if direktifini kullanabiliyoruz. Button kontrolündeki olayları betikteki bir fonksiyonla ilişkilendirirken @click şeklindeki element adı ele alınıyor. Modeldeki özellikleri kontrollerde gösterirkense {{propertyName}} notasyonuna başvuruyoruz.</p>
<p>Örneğimizdeki bileşen, önyüz tasarımı ve kodu aynı dosya içerisinde barındırmakta. Ancak hazır olarak gelen şablonu incelerseniz Components klasöründe bir bileşen geldiğini de görebilirsiniz. Yani alt bileşenleri bu klasör altında da toplayabiliriz. Bu arada kodlarda yakaladığınız yorum satırlarını okumayı unutmayın. Destekleyici bilgiler görebilirsiniz.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><template>
<div id="app">
<h2>Bölüm adını yazar mısın?</h2>
<section class="input-Section">
<input type="text" v-model="query">
<button :disabled="!query.length" @click="findEpisode">Göster</button>
<!-- butona basılınca findEpisode metodu çağırılacak -->
</section>
<section v-if="error">
<!-- error değişkeni true olarak set edilmişse bir şeyler ters gitmiştir -->
<i>Sanırım bölüm bulunamadı ya da bir şeyler ters gitti</i>
</section>
<section v-if="!error">
<!-- Aranan veri bulunduysa -->
<h1>{{name}} ({{season}}/{{number}}) - {{ airdate }}</h1>
<div><p>{{summary}}</p></div>
<div>
<img :src="imageLink"/>
</div>
</section>
</div>
</template>
<script>
export default {
name: "Pilot",
data() {
// data modelimiz api servisinden dönen tipe göre düzenlendi
return {
query: "",
error: false,
id: null,
name: "",
airdate:"",
season: null,
number: null,
summary: "",
imageLink: ""
};
},
methods: {
findEpisode() {
// api servisine talep gönderen metod
this.$http
.get(`/episode/${this.query}`) // sorguyu tamamlıyoruz. parametre olarak input kontrolüne girilen değer alınıyor. query değişkeni üzerinden.
.then(response => {
this.error = false;
this.name = response.data.name; // servisten gelen cevabın içindeki alanların, vue data modelindeki karşılıklarına ataması yapılıyor
this.season = response.data.season;
this.number = response.data.number;
this.summary = response.data.summary;
this.airdate=response.data.airDate;
this.imageLink=response.data.imageLink;
console.log(response.data); //control amaçlı
})
.catch(() => {
// hata alınması durumu
this.error = true;
this.name = "";
});
}
}
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding:10px;
text-align: center;
color: #2c3e50;
margin-top: 10px;
}
input {
width: 75%;
outline: none;
height: 20px;
font-size: 1em;
}
button{
display: block;
width: 25%;
height: 25px;
outline: none;
border-radius: 4px;
white-space: nowrap;
margin:0 10px;
font-size: 1rem;
}
.input-Section {
display: flex;
align-items: center;
padding: 20px 0;
}
</style></pre>
<p>App bileşeninde dikkat edileceği üzere $http ile yapılan bir servis çağrısı var. Bu axios tarafından sağlanacak bir hizmet. Bu nedenle main.js dosyasında gerekli hazırlıkların yapılması lazım. Dikkat edileceği üzere Vue çalışma zamanının axios'u $http özelliği üzerinden kullanabilmesini sağlayacak bir enjekte işlemi söz konusu.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">import Vue from 'vue'
import App from './App.vue'
import axios from 'axios' // API servisine HTTP talebini göndermek için kullandığımız modül
axios.defaults.baseURL = 'http://localhost:4001/api/'; // base url adresini atadık
Vue.http = Vue.prototype.$http = axios;
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')</pre>
<p>Bu konu kapsamı dışında ancak .Net Core tabanlı bir Web API hizmetimiz de bulunuyor. Bu servis dizinin bölümlerini aramak amacıyla kodladığımız sahte bir program. Konumuzla doğrudan ilintili olmadığı için detayına girmemize gerek yok ama en azından Controller sınıfında neler yaptığımıza bir bakalım derim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
namespace bigbangapi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class EpisodeController : ControllerBase
{
[HttpGet("{name}")]
public ActionResult<Episode> Get(string name)
{
try
{
string db = System.IO.File.ReadAllText("db/content.json");
JObject json = JObject.Parse(db);
JArray episodes = (JArray)json["episodes"];
var all = episodes
.Select(e => new Episode
{
Id = (int)e["id"],
Name = (string)e["name"],
Season = (int)e["season"],
Number = (int)e["number"],
Summary = (string)e["summary"],
ImageLink = (string)e["image"]["medium"],
AirDate=(string)e["airdate"]
});
var result = all.Where(e => e.Name == name).FirstOrDefault();
return new ActionResult<Episode>(result);
}
catch
{
return NotFound();
}
}
}
}</pre>
<p>Örneğin basitliği açısından yalın bir Get operasyonu sunuyoruz. Parametre olarak gelen bölüm adını fiziki olarak tuttuğumuz content.json içeriğinde arayarak bir sonuç döndürmekteyiz. Pek tabii bu sahte bir servis. Veri kaynağı olarak fiziki dosya yerine veri tabanı kullanılan bir moda da geçebiliriz. Hatta film bilgileri sunan bir gerçek hayat API'sini de tercih edebiliriz. Tercih size kalmış.</p>
<p>Ah unutmadan! Geliştirme safhasında kuvvetle muhtemel CORS<em>(Cross Origin Resource Sharing)</em> ile ilgili bir sorun yaşayabilirsiniz. Bu nedenle Startup.cs içerisinde CORS özelliğini etkinleştirmemiz ve masaüstünden gelecek cevapları kabul edebileceğimizi belirtmemiz gerekiyor.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Diğer uygulamanın node.js servisinin buraya axios üzerinden
// talep atabilmesi için Cors desteği eklenmiştir
// Configure metodu içerisinde de 8080 kaynağından gelecek
// tüm metodlar için izin yetkisi bildirilmiştir.
services.AddCors();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors(
options=>options.WithOrigins("http://localhost:8080").AllowAnyMethod()
);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
//app.UseHttpsRedirection();
app.UseMvc();
}</pre>
<p>Tekrar Vue tarafına dönerek ilerleyelim. Uygulamanın giriş noktasını belirtmek için package.json dosyasına main özelliğini eklememiz ve bir adres yönlendirmesi yapmamız gerekiyor. Bu sayede uygulama kodunda yapılan her değişiklik anında çalışma zamanına da yansıyacaktır<em>(Program çalıştıktan sonra önyüz bileşeni olan App.vue dosyasında değişiklikler yapmayı deneyin)</em></p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">"main": "http://localhost:8080",</pre>
<h2>Çalışma Zamanı</h2>
<p>Normalde desktop uygulamasını çalıştırmak için proje klasöründeyken birinci terminalden</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">npm run serve</pre>
<p>ile sunucuyu etkinleştirmek ve ardından ikinci bir terminal penceresinden</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">./node_modules/.bin/run .</pre>
<p>yazmak gerekiyor. Lakin bu durumda NW.js'in ilgili SDK'sı indirilip development ortamı ayağa kalkıyor. Bunu otomatikleştirmek için nw@sdk isimli paketi yüklemek ve package.json dosyasındaki script bölümüne örneğin <em>desktop</em> isimli yeni bir çalışma zamanı parametresi dahil etmemiz yeterli.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false"> "scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"desktop": "nw ."
},</pre>
<p>Desktop uygulaması çalıştıktan sonra tarayıcının Development Tools'unu kullanarak debug yapılması mümkün. Masaüstü tarafından yapılan API çağrılarını ve dönen sonuçları buradan izleme şansımız var. Tabii tüm bunların başında yazdığımız web api servisinin de çalışır durumda olması gerekiyor öyle değil mi? Sonrasında Node.js server ve desktop uygulaması çalıştırılarak ilerlersek yerinde olacaktır. Bunları üç ayrı terminal penceresinden yürütebiliriz ama temel olarak aşağıdaki komutları kullanmamız lazım.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet run
npm run serve
npm run desktop</pre>
<p>Eğer bir sorun olmazsa uygulama ayağa kalktıktan sonra Big Bang Theory'den örnek bir bölümü aratabiliriz. Ben aşağıdaki gibi bir sonuca ulaşmışım.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/16/cover_1.png" alt="" /></p>
<h2>Paketleme</h2>
<p>Uygulamayı paketlemek çok daha mantıklı ve gerekli elbette. Sonuçta dağıtımını<em>(Deployment)</em> yapmak isteyeceğiz. Bunun için packages.json içerisine build bölümünü aşağıdaki gibi eklememiz lazım.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false"> "build": {
"nwVersion": "0.35.5"
}</pre>
<p>Dikkat edileceği üzere nw paketinin hangi versiyonunu kullanacağımızı belirtiyoruz<em>(Güncel sürümüne bakmanızda yarar var)</em> bbtheory isimli uygulamanın root klasöründe aşağıdaki komut ile 64bit linux platformu için gerekli paketin üretilmesi sağlanabiliyor.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">./node_modules/.bin/build --tasks linux-x64 .</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/16/cover_2.png" alt="" /></p>
<p>Paket boyutu oldukça yüksek görüldüğü üzere! Zaten cross-platform masaüstü uygulamaları için en rahatsız edici konuların başında da dosya boyutları geliyor. Ancak küçültmek için çeşitli yollar olduğu ifade edilmekte. Bunu henüz araştırma fırsatım olmadı ancak <a href="https://dev.to/thejaredwilcurt/reducing-app-distribution-size-in-nwjs-3d5f" target="_blank">yakın tarihli şu yazıda bir takım bilgiler</a> mevcut.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/16/cover_3.png" alt="" /></p>
<h2>Ben Neler Öğrendim?</h2>
<p>Elbette aptal kutunun başında saatlerimi geçirdiğim Perfect Strangers dizisinin bana alttan alttan verdiği mesajlar gibi bu örnek çalışma sonrasında öğrendiğim bazı şeyler de olmadı değil. Bunları aşağıdaki gibi özetlemeye çalışayım.</p>
<ul>
<li>Vue tarafında ön yüz nasıl geliştirilir</li>
<li>v-model, v-if, {{ }}, @click gibi Vue ilişkili ifadeler ne işe yarar</li>
<li>Bileşen ile model özellikleri nasıl kullanılır</li>
<li>axios ile node.js tarafından servis talepleri nasıl gönderilir</li>
<li>newtonsoft.json ile bir json dizisinde nasıl linq sorgusu çalıştırılır</li>
<li>CORS ne işe yarar</li>
</ul>
<p>Ne yazık ki Vue konusunda uzman değilim. Aslında onu şirketteki yeni nesil projelerde kullanıyoruz lakin iyi bir başlangıcım yok. Belki de ahch-to<em>(macOS High Sierra)</em> üzerinde yapacağım ikinci faz çalışmaları kapsamında ona daha fazla zaman ayırabilirim. Böylece geldik neşeli <a href="https://github.com/buraksenyurt/saturday-night-works/tree/master/No%2016%20-%20Build%20Desktop%20App%20with%20Vue%20and%20NWjs" target="_blank">bir cumartesi gecesinin 16ncı bölümüne ait derlemeler</a>in de sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2019-06-14T13:00:00+00:00vue.jselectronlinuxcross platform developmentnw.jsnodenode.js.net coreweb apirestcorsdotnetchromiumnode-webkitbsenyurtDaha önceden Electron ile cross platform desktop uygulamalarının geliştirilmesi üzerine çalışmıştım. Bu kez eskiden node-webkit olarak bilinen NW.js kullanarak WestWorld üzerinden desktop uygulaması geliştirmek istedim. NW.js cephesinde de aynen Electron'da olduğu gibi Chromium, Node.js, HTML, CSS ve javascript kullanılmakta. Lakin ufak tefek farklılıklar var. Electron'da entry point yeri Javascript script'i iken NW.js tarafında script haricinde bir web sayfası da giriş noktası olabiliyor. Build süreçlerinde de bir takım farklılıklar var.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=0f41f4f0-5e65-4af7-96cb-e0644e414c4a0https://buraksenyurt.com/trackback.axd?id=0f41f4f0-5e65-4af7-96cb-e0644e414c4ahttps://buraksenyurt.com/post/vue-ve-nw-js-ile-desktop-uygulamasi-gelistirmek#commenthttps://buraksenyurt.com/syndication.axd?post=0f41f4f0-5e65-4af7-96cb-e0644e414c4a