KnockoutJS’i Derinlemesine İnceleme
Javascript Frameworkleri arasından en beğendiğim olan KnockoutJS ile kullanıcı etkileşimini arttırmak; kapsamlı ekranlarda, karmaşıklığı ve kod kalabalığını azaltmak esas amaç edinilmiştir. Bu şekilde model ve tasarım mantığı da birbirinden ayrılmış olur. KnockoutJS’de MVVM Presentation Model mimarisi kullanılmaktadır. WPF ve Silverlight teknolojileri için de ayni mimari kullanılmaktadır. Bu teknoloji view’ın, modeli bilmesinin gerekliliğini ortadan kaldırığı gibi, arayüzü yapan kişinin de arka tarafta nasl bir iş geliştirildiğini bilmeden bağımsız olarak çalışmasını sağlar. Bunların yanında arka taraftaki geliştirici için de genişletilebilir,bakım yapılabilir ve test edilebilir bir yapıya izin vermektedir.
Image Source : https://www.codeproject.com/KB/Articles/1070289/Knipsel1.PNG
M:Model yani uygulama içerisinde kullanacağımız data.V:View datanın sunulduğu katman VM:ViewModel Model ile view’ı bağlayan(Binding) yapıdır.View, ViewModel sayesinde model’e erişir ve kullanıcı etkileşimine cevap verir. Sayfamıza öncelikle alttaki JS dosyalarımızı ekliyoruz.Yani knockout ve jquery.
1 2 |
<script src="~/Scripts/knockout-3.1.0.js" type="text/javascript"></script> <script src="~/Scripts/jquery-2.1.1.min.js"></script> |
1. Öncelikle adımızı, soyadımızı ve tam adımızı yazan 3 alanı aşşağıda görüldüğü gibi oluşturalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<form> <table> <tr> <td style="WHITE-SPACE: nowrap"> <h2>Knockout Traning</h2> <p>First Name:<span <strong>data-bind</strong>="text: firstName"></span></p> <p>First Name:<input <strong>data-bind</strong>="value: firstName"></input></p> <p>Last Name:<span data-bind="text: lastName"></span></p> <p>Last Name:<input data-bind="value: lastName"></input></p> <p>Full Name:<span data-bind="text: fullName"></span></p> </td> </tr> </table> </form> |
Fark ettiğiseniz data-bind=”text: firstName” diye bir objectimiz var.Binding data, nesnenin text’ine bağlandı.Datada ki karşılığı firstName’dir.Kısaca data ile nesneleri birbirne bağladık(Bind) ettik. Şimdi aşşağıda görüldüğü gibi data’ya bakalım.Yani ViewModel’e:
1 2 3 4 5 6 7 8 9 10 11 |
<script type="text/javascript"> function viewModel() { firstName = ko.observable("Bora"), lastName = ko.observable("Kaşmer"), fullName = ko.computed(function() { return this.firstName() + " " + this.lastName() }), }; } $(document).ready(function () { ko.applyBindings(viewModel()); }); </script> |
Yukarıda görüldüğü üzere property değerleri yani firstName ve lastName sanki datadan çekilmiş gibi direk atandı.Komut Observable’dır. fullName için Computed yani hesap komutu kullanıldı.Ad ve Soyad birleştirildi.Aslında fullName ==>firstName ve lastName’e bağlandı. Artık herhangi birinde oluşacak değişiklik fullName ‘i de direk etkileyecek ve onu da değiştirecektir. Bizim fullName için ayrıca bir işlem yapmamıza gerek kalmayacaktır. Buna bağlı <span>’ lar da bind yolu ile otomatik olarak değişecektir. Aşşağıda konu ile ilgili ekran çıktısı görülmektedir. First Name değişmiştir ve bundan etkilenen Full Name’de buna bağlı olarak değişmiştir.
Ayrıca yukarıdaki kodda görüldüğü üzere viewModel’imizi knockout Objesine Bind etmeyi unutmuyoruz. ko.applyBindings.
2.Şimdi modelimize aşşağıda görüldüğü gibi arkadaşlarımızı ekleyelim, tabi view’ımızada. Firend’de kendi basına bir modeldir.Ve ViewModelimize array bir dizi olarak eklenecektir.observableArray bu işe yaramaktadır.
1 2 3 4 5 6 7 |
function friend(name) { var date = new Date(); var tim = date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() + ":" + date.getMilliseconds(); return { name: ko.observable(name + " " + tim) }; } |
1 2 3 4 |
function viewModel() { . friends = ko.observableArray([new friend("Bora"), new friend("Engin")]) } |
View’ımız da aşşağıda görüldüğü gibi KnockOut Template yapısını kullanacağız. En üstten toplam arkadaş sayısını yazdık.friends dataları içinde gezilerek ilgili şablon dolduruldu.
1 2 3 4 5 6 7 |
<h2>Friends (<label data-bind="text: friends().length"></label>)</h2> <div data-bind="template: { name: 'friendsTemplate', foreach: friends }"></div> <script id="friendsTemplate" type="text/html"> <li> <input data-bind="value: name"></input> </li> </script> |
Yukarıda görüldüğü gibi template name ile script name’i aynı olması geremektedir.
3.Şimdi aşşağıda görüldüğü gibi arkadaşlarımızın twitter’ı var mı. Var ise adının girilmesini sağlıyalım. Ayrıca listemize yeni arkadaş ekliyip,çıkarma gibi eventleri yani addFriend, removeFriend ve friend model’e de isOnTwitter ve twitterName şeklinde iki property ekliyelim.
1 2 3 4 5 6 7 8 9 |
function friend(name) { var date = new Date(); var tim = date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() + ":" + date.getMilliseconds(); return { name: ko.observable(name + " " + tim), isOnTwitter: ko.observable(false), twitterName: ko.observable() }; } |
Aşşağıda görüldüğü gibi ViewModel’imize üç property ekledik. Bunlardan notTweeterUser property iken addFirend ve removeFriend ise belli bir event’de çağrılacak olan functionlardır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function viewModel() { firstName = ko.observable("Bora"), lastName = ko.observable("Kaşmer"), fullName = ko.computed(function() { return this.firstName() + " " + this.lastName() }), friends = ko.observableArray([new friend("Bora"), new friend("Engin")]), notTweeterUser = ko.computed(function () { return ko.utils.arrayFilter(friends(), function (friend) { return !friend.isOnTwitter() }); }), addFriend = function() { friends.push(new friend("Anaother")); }, removeFriend = function(name) { friends.remove(name); } } |
Yukarıda görüldüğü gibi notTweeterUser ‘da ko.utils altındaki arrayFilter kullanılarak friends modelindeki isOnTwitter==false olanlar alınmıştır. Tam isimde olduğu gibi ko.computed() function’ı içinde bu filitreleme işlemi yapılmaktadır.
Yukardaki kodun içindeki bu önemli küçük kod parçası aşşağıda ayrıca gösterilmiştir.
1 2 3 |
notTweeterUser = ko.computed(function () { return ko.utils.arrayFilter(friends(), function (friend) { return !friend.isOnTwitter() }); }) |
addFriend’de default olarak Anaother isimli twitter’ı olmayan bir friend eklenecektir. View’ımızı da alttaki gibi oluşturalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<form> <table> <tr> <td style="WHITE-SPACE: nowrap"> <h2>Knockout Traning</h2> <button data-bind="click: save">Save</button> <p>First Name:<span data-bind="text: firstName"></span></p> <p>First Name:<input data-bind="value: firstName"></input></p> <p>Last Name:<span data-bind="text: lastName"></span></p> <p>Last Name:<input data-bind="value: lastName"></input></p> <p>Full Name:<span data-bind="text: fullName"></span></p> <h2>Friends (<label data-bind="text: friends().length"></label>)</h2> Tweeter'da olmayan <label data-bind="text: notTweeterUser().length"></label> arkadaşın var! <div data-bind="template: { name: 'friendsTemplate', foreach: friends }"></div> <script id="friendsTemplate" type="text/html"> <li> <input data-bind="value: name"></input> <button data-bind="click: removeFriend">Remove Firend</button> <label> <input type="checkbox" data-bind="checked: isOnTwitter" /> Is On Twitter</label> <input data-bind="value: twitterName, visible: isOnTwitter"></input> </li> </script> <br /> <button data-bind="click: addFriend, enable: friends().length < 5">Add Firend</button> </td> </tr> </table> </form> |
Admız,Soyadımız,Tam isim
1 |
Tweeter'da olmayan <label data-bind="text: notTweeterUser().length"></label> arkadaşın var! |
Yukarıda görüldüğü gibi tweeter’ı olmayan arkadaş sayısı(notTweeterUser().length).Dikkat!!:Function’ı çağrırken () parantezleri unutmayalım. friendTemplate’e removeFriend şeklinde bir button getirilmiştir. Click event’i removeFriend functionını çağrır. Checkbox nesnesinin checked propertysine isOnTwitter bağlanmıştır. twitterName‘de koşul vardır. Knockout’da koşul mantığı çok kullanışlıdır.Yukarıdaki koddan alınmış bu küçük kod parçacığıda aşşağıdaki gibi visible: isOnTwitter koşulu alkışı hak etmektedir.
1 |
<input data-bind="value: twitterName, visible: isOnTwitter"></input> |
Gene Add Friend Buttonunda da koşul vardır.Toplam arkadaş sayısı 5’den fazla ise tıklanamıyacaktır. Yukarıdaki koddan alınmış bu küçük kod parçacığıda aşşağıdaki gibidir.
1 |
<button data-bind="click: addFriend, enable: friends().length < 5">Add Firend</button> |
Şimdi genel resme bir bakalım.Tüm kontroller birbirlerine bağlıdır.
- AddFriend butonuna basılnca viewModel’deki friends’e aşşağıdaki gibi bir friend eklenecektir.
-
12addFriend = function() {friends.push(new friend("Anaother"));
- friends’e bakan template yeni firend’i Anaother ismi ile ve isOnTwitter false olduğu için twitter ismi gelmeyen kaldır butonu ile yeni bir kayıdı otomatik olarak ekler. İlgili kod parçacığı aşşağıdadır.
-
1<div data-bind="template: { name: 'friendsTemplate', foreach: friends }"></div>
- Buna bağlı olarak toplam arkadaş sayısı ve twitterı olmayan arkadaş sayıları bir artar. İlgili kod parçacığı yine aşşağıdadır.
-
12<h2>Friends (<label data-bind="text: friends().length"></label>)</h2>Tweeter'da olmayan <label data-bind="text: notTweeterUser().length"></label> arkadaşın var!
- Artan yeni firends sayısı 5’e çıkmış ise AddFriend buttonunun enable’ı false olur.İlgili kod parçacığı aşşağıdadır.
-
1<button data-bind="click: addFriend, enable: friends().length < 5">Add Firend</button>
Görüldüğü üzere tüm view birbirine bağlıdır. Ve bir alandaki değişiklik hiç kod yazmadan tüm yapıyı domino taşı gibi tetiklemektedir. Şimdi gelelim kaydetme işlemine: Kaydetme işlemini AjaxPost ile JSON data göndererek yapıcaz. Demek ilk ihtiyacımız olan veriyi json tipine çevirmek. Viewda tablomuzun başına alttaki kodu koyalım:
1 2 3 |
<td valign="top"> <textarea name="friends" data-bind="value: ko.toJSON(friends)" style="width: 400px; height: 400px;"></textarea> </td> |
Bir Textarea içine knockout function’ı olan toJSON()’u kullandık.firends modelimizin tamamını bu textarea içine json olarak dynamic şekilde gömüyoruz. Bu demek oluyor ki yeni bir arkadaş eklediğimizde textarea içindeki json text değişecek.isOnTwitter CheckBox’ı tıklanınca firends json değişecek. Buna bağlı twitterName gözükecek.İlgili kod parçacığı aşşağıdadır.
1 |
<input data-bind="value: twitterName, visible: isOnTwitter"></input> |
Aşşağıdaki ekran çıktısında görüldüğü gibi ismi doldurduğumuz takdirde friend json anlık olarak değişecek.Ve biz herhangi bir emek harcayıp kod yazmadan, gönderilecek datayı textarea içinden post edebilecez.
Şimdi sırada bu dataya uygun Modelmizi oluşturmakta. İlgili kod aşşağıdadır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace KnockoutSteveSanderson.Controllers { public class Person { public string firstName { get; set; } public string lastName { get; set; } public ICollection<Friend> friends { get; set; } } public class Friend { public string name { get; set; } public bool isOnTwitter { get; set; } public string twitterName { get; set; } } } |
Controller tarafındaki kodumuz yine aşşağıda görüldüğü gibidir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
namespace KnockoutSteveSanderson.Controllers { public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { return View(); } public JsonResult Save(List<Friend> friends) { System.Text.StringBuilder sb = new System.Text.StringBuilder(); string message = "You Have Total '" + friends.Count.ToString() + "' Friends " + sb.Append(' ', 60) +" And '" + friends.Count(f => f.isOnTwitter == true).ToString() + "' of them has on Twitter!"; return Json(new {message}); } } } |
Yukarıda görüldüğü üzere json’l gönderdiğimiz data property bazında friend classımızla eşleşmektedir. Textarea’da array dizisi olarak friendler codebase’de List<Friend>()’e denk gelmektedir. Yine dönüş tipimiz JsonResult’dır. Toplam arkadaş sayısı ve Twitter’ı olan arkadaş sayısı gönderilmektedir. Client Tarafındaki Save function’ı aşşağıda görüldüğü gibi gene viewModel’e function olarak bağlanmıştır. data:ko.toJSON(friends) textarea’daki textin ta kendisidir. success durumunda dönen mesaj aşşağıda görüldüğü gibi yazdırılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function viewModel() { ., ., save = function () { $.ajax({ url: "@Url.Action("Save")", type: "post", data: ko.toJSON(friends), contentType: "application/json", success: function(result) { alert(result.message); } }); }; } |
Görüldüğü üzere knockoutJS komplike ve kullanıcı etkileşiminin çok olduğu sayfalarda büyük kolaylık sağlamaktadır.Kod kalabalığını minimuma indirmesinin yanında, ileride yapılabilecek değişiklik veya eklentilere karşı çok hızlı cevap vermemize olanak sağlamaktadır.
Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere herkese hoşçakalın.
Not:Örnek Url (İptal): http://knockoutfriendlist.azurewebsites.net/
Source Code: http://www.borakasmer.com/projects/KnockoutSteveSanderson.rar
Güzel makale, teşekkürler :)
Teşekkürler.
Cok guzel bir yazi, ellerine saglik
Oldukça faydalı, açıklayıcı ve güzel bir makale olmuş.
Emeğine sağlık, teşekkürler.
Yeni başlayanlar için faydalı bir makale olmuş.
Eline sağlık
Teşekkür ederim Erhan..