3. Angular istemcisi JS
3.1. Angular JS Çerçevesi Referansları
Bu belgenin başında Angular JS çerçeve için iki referans verilmişti. Bunları burada tekrar veriyoruz:
- [ref1]: Adam Freeman tarafından yazılan ve Apress yayınevi tarafından basılan "Pro AngularJS" adlı kitap. Bu, mükemmel bir kitaptır. Bu kitaptaki örneklerin kaynak kodları, URL [http://www.apress.com/downloadable/download/sample/sample_id/1527/] adresinden ücretsiz olarak edinilebilir;
- [ref2]: Angular'ın resmi belgeleri JS [https://docs.angularjs.org/guide];
Angular JS, tek başına bir kitaba konu olacak kadar önemli bir konudur. Adam Freeman’ın kitabı 600 sayfadan fazladır ve bu sayfaların hiçbiri boşa harcanmamıştır. Bir Angular uygulamasını anlatacağız ve bu anlatım sırasında bu çerçevenin temellerinden bahsetmek zorunda kalacağız. Bununla birlikte, sadece önerilen çözümü anlamak için gerekli açıklamalarla yetineceğiz. Angular son derece zengin bir çerçeve ve aynı sonuca ulaşmak için pek çok çözüm mevcut. Bu bir zorluktur; çünkü başlangıçta, kullandığımız çözümün diğerlerine göre daha mı iyi yoksa daha mı kötü olduğunu bilemeyiz. Burada önerilen çözüm de buna bir örnektir. Bu çözüm farklı şekilde ve belki de daha iyi uygulamalarla yazılabilirdi.
3.2. Angular istemci mimarisi
Angular istemci mimarisi, birkaç fark dışında klasik bir web uygulamasının mimarisine benzer. Örneğin, bir Spring web uygulaması şu mimariye sahiptir:
![]() |
Bir istemciden gelen isteğin işlenmesi şu şekilde gerçekleşir:
- istek - istenen URL'ler http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... biçimindedir. [Dispatcher Servlet], gelen URL'leri işleyen Spring sınıfıdır. Bu sınıf, URL'i işlenmesi gereken eyleme "yönlendirir". Bu eylemler, [Contrôleurs] olarak adlandırılan belirli sınıfların yöntemleridir. Burada MVC'in C kısmı, [Dispatcher Servlet, Contrôleur, Action] dizesidir. Gelen URL'i işlemek için herhangi bir eylem yapılandırılmamışsa, [Dispatcher Servlet] servleti, istenen URL'in bulunamadığını bildirir (404 hatası NOT FOUND);
- işleme
- Seçilen eylem, parami servletinin kendisine ilettiği [Dispatcher Servlet] parametrelerini kullanabilir. Bu parametreler çeşitli kaynaklardan gelebilir:
- [/param1/param2/...] yolundaki URL’ten,
- [p1=v1&p2=v2]'in URL,
- tarayıcı tarafından isteği ile birlikte gönderilen parametreler;
- kullanıcının isteğinin işlenmesi sırasında, eylem [metier] ve [2b] katmanlarına ihtiyaç duyabilir. İstemcinin isteği işlendikten sonra, bu işlem çeşitli yanıtları tetikleyebilir. Klasik bir örnek şudur:
- istek düzgün bir şekilde işlenemediğinde bir hata sayfası
- aksi takdirde bir onay sayfası
- eylem, belirli bir görünümün ([3]) görüntülenmesini ister. Bu görünüm, görünüm modeli olarak adlandırılan verileri gösterecektir. Bu, MVC'teki M'dir. Eylem, bu M şablonunu ([2c]) oluşturacak ve bir V görünümünün ([3]) görüntülenmesini isteyecektir;
- yanıt - seçilen V görünümü, eylemin oluşturduğu M modelini kullanarak, müşteriye göndermesi gereken HTML yanıtının dinamik kısımlarını başlatır ve ardından bu yanıtı gönderir.
Angular istemcimizin mimarisi de buna benzer olacaktır, ancak terminoloji biraz farklıdır. Öncelikle, Angular uygulamaları genellikle tek sayfalık web uygulamaları (APU) veya Tek Sayfalı Uygulamalar (SPA)dır:

- Kullanıcı, uygulamanın ilk URL sayfasını şu biçimde ister: http://machine:port/contexte. Tarayıcı, istenen belgeyi almak için bir web sunucusuna sorgu gönderir. Bu belge, HTML ile stilize edilmiş ve Javascript ile dinamik hale getirilmiş bir sayfadır;
- ardından kullanıcı kendisine sunulan görünümlerle etkileşime girecektir. Çeşitli etkileşim türleri ayırt edilebilir:
- dış dünya ile herhangi bir etkileşim gerektirmeyenler, örneğin görünümdeki öğeleri gizleme/gösterme. Bunlar gömülü JavaScript tarafından işlenir;
- uzaktaki bir web hizmetinden gelen verileri gerektiren etkileşimler. Bu veriler bir AJAX (Asynchronous JavaScript and XML) çağrısı ile alınacak, bir model oluşturulacak ve bir görünüm görüntülenecektir;
- başlangıç görünümünden farklı bir görünüm gerektirenler. Bu, başlangıç sayfasını sunan sunucuya bir Ajax çağrısı ile talep edilecektir. Ardından önceki süreç tekrarlanacaktır. Alınan sayfa tarayıcıda önbelleğe alınacaktır. Bir sonraki çağrıda, uzak HTML sunucusuna talep edilmeyecektir;
Sonuç olarak, tarayıcı yalnızca tek bir HTTP çağrısı yapar; bu, başlangıç sayfasını alan çağrıdır. HTTP sunucusuna veya uzak web hizmetlerine yapılan sonraki HTML çağrıları, sayfalara gömülü JavaScript tarafından gerçekleştirilir.
Şimdi tarayıcı içindeki uygulamanın mimarisini sunacağız. Uygulamanın HTML sayfalarını sunan HTML sunucusunu göz ardı edeceğiz. Açıklama amacıyla, bunların hepsinin tarayıcının önbelleğinde mevcut olduğunu varsayabiliriz.
![]() |
Öncelikle, bu mimariyi bir yere oturtmamız gerekiyor:
- [1]'te bir tarayıcı içindeyiz;
- [2]'te, bir kullanıcı tarayıcının görüntülediği sayfalarla etkileşim kuruyor;
- [3]'te, veriler ağ üzerinden, genellikle web hizmetlerinden aranır;
Kullanıcı görünümlerle etkileşime girer: formları doldurur ve onaylar. Bu süreci yukarıdaki V1 görünümüyle açıklayalım. Bunun uygulamanın başlangıç görünümü olduğunu varsayalım. Bu görünüm şu şekilde elde edilmiştir:
- kullanıcı, uygulamanın başlangıç görünümü olan URL'i şu biçimde talep eder: http://machine:port/contexte;
- tarayıcı, bu URL ile ilişkili belgeyi talep etmiştir. V1 görünümünden HTML / CSS / JS sayfasını aldı;
- sayfaya gömülü JavaScript devreye girerek kontrolü C1 [5] denetleyicisine devretti;
- bu denetleyici, V1 görünümünün M1, [8] ve [9] modellerini oluşturdu. Bu modelin oluşturulması sırasında [6] numaralı dahili hizmetlerin kullanılması ve [7] numaralı harici hizmetlere sorgu gönderilmesi gerekmiş olabilir;
Kullanıcının önünde şimdi V1 görünümü bulunmaktadır. Bunun bir form olduğunu varsayalım. Kullanıcı formu doldurur ve ardından onaylar:
- [4]'te kullanıcı formu onaylar;
- [5]'te, bu olay C1 denetleyicisinin yöntemlerinden biri tarafından işlenecek;
Eğer olay sadece V1 görünümünde basit bir değişikliğe yol açarsa (alanları gizleme / gösterme), C1 denetleyicisi, V1 görünümünün M1 şablonunu değiştirecek ve ardından V1 görünümünü yeniden görüntüleyecektir. Bunu yapmak için [services] veya [6] katmanındaki hizmetlerden birine ihtiyaç duyabilir.
Olay harici veriler gerektiriyorsa:
- [6]'te ise, C1 denetleyicisi, bu verileri elde etmek için [DAO] katmanından talepte bulunur;
- [7]'te, bu katman verileri elde etmek için bir veya daha fazla AJAX çağrısı yapacaktır;
- [8] ve [9]'te, M1 modeli değiştirilecek ve V1 görünümü görüntülenecektir;
Olay bir görünüm değişikliğine yol açarsa, önceki iki durumda da V1 görünümünü görüntülemek yerine, C1 denetleyicisi yeni bir URL ve [10] isteyecektir. Bu, tarayıcı içindeki bir URL'tir. Bu durum, HTTP çağrısının HTML sayfa sunucusuna hemen iletilmesine yol açmaz. Bu URL değişikliği, her bir dahili URL'e bir V görünümü ve onun C denetleyicisi karşılık gelecek şekilde yapılandırılmış bir yönlendirici tarafından işlenir. Yönlendirici daha sonra yeni Vn görünümünü görüntületir. Görüntülenmeden önce, kontrolörü Cn devreye girer, Mn şablonunu oluşturur ve ardından Vn görünümü [11]'i görüntületir. Vn görünümüne ait HTML sayfası tarayıcının önbelleğinde bulunmuyorsa, bu sayfa HTML sayfa sunucusundan istenir.
Bu mimarinin [Présentation] katmanı, JSF (Java Server Faces) mimarisine yakındır:
- V görünümü, JSF'teki Facelet türü görünüme karşılık gelir;
- C denetleyicisi, hem V görünümünün M modelini hem de olay işleyicilerini içeren bir Java sınıfı olan JSF bean'ine karşılık gelir;
[Services] katmanı, alıştığımız [Services] katmanlarından farklıdır. Sunucu tarafı web geliştirmede, genellikle aşağıdaki katman mimarisi kullanılır:
![]() |
Yukarıda, [web] katmanı, [DAO] katmanıyla yalnızca [métier] katmanı aracılığıyla iletişim kurmaktadır. [web] katmanına, bu iletişimi sağlayacak [DAO] katmanına bir referans eklememizi engelleyen hiçbir şey yoktur. Ancak bunu yapmıyoruz.
Angular ile ise bunu kendimize yasaklamıyoruz. Böylece mimari şu şekilde oluyor:
![]() |
- [1]'te, [présentation] katmanı herhangi bir hizmetle doğrudan iletişim kurabilir;
- [2]'te ise hizmetler birbirlerini tanır. Bir hizmet, bir veya daha fazla başka hizmeti kullanabilir.
3.3. Angular istemci görünümleri
Angular istemci görünümleri, 1.3.3. paragrafında daha önce ele alınmıştı. Bu yeni bölümün okunmasını kolaylaştırmak için, bunları burada tekrar veriyoruz. İlk görünüm şöyledir:
![]() |
- [6], uygulamanın giriş sayfası. Bu, doktorlar için randevu alma uygulamasıdır;
- [7]'te, [debug] modunda olup olmayacağını belirleyen bir onay kutusu bulunur. Bu mod, geçerli görünümün şablonunu görüntüleyen [8] çerçevesinin varlığıyla karakterize edilir;
- [9]'te, milisaniye cinsinden yapay bir bekleme süresi. Varsayılan değeri 0'dır (bekleme yok). N bu bekleme süresinin değeri ise, kullanıcının her eylemi N milisaniyelik bir bekleme süresinden sonra yürütülür. Bu, uygulama tarafından uygulanan bekleme yönetimini görmeyi sağlar;
- [10]'te, Spring 4 sunucusunun URL'i yer alır. Yukarıdakileri takip edersek, bu [http://localhost:8080]'tir;
- [11] ve [12]'te ise uygulamayı kullanmak isteyen kişinin kullanıcı adı ve şifresi yer almaktadır. İki kullanıcı vardır: bir rol (ADMIN) ile admin/admin (kullanıcı adı/şifre) ve bir rol (USER). Yalnızca ADMIN rolüne sahip kullanıcılar uygulamayı kullanma hakkına sahiptir. USER rolü, bu kullanım durumunda sunucunun ne yanıt verdiğini göstermek amacıyla eklenmiştir;
- [13], sunucuya bağlanmayı sağlayan düğme;
- [14]'te ise uygulamanın dili gösterilmektedir. İki dil seçeneği vardır: varsayılan olarak Fransızca ve İngilizce.
![]() |
- [1]'te, bağlantı kurulur;
![]() |
- giriş yaptıktan sonra, randevu almak istediğiniz doktoru [2] ve randevu gününü [3] seçebilirsiniz;
- [4] ile seçilen gün için seçilen doktorun randevu takvimini görüntüleyebiliriz;
![]() |
- doktorun randevu takvimini aldıktan sonra, bir randevu aralığı rezerve edebilirsiniz [5];
![]() |
- [6]'te randevu için hasta seçilir ve bu seçim [7]'te onaylanır;
![]() |
Randevu onaylandıktan sonra, otomatik olarak takvime geri dönülür ve yeni randevu artık takvime kaydedilmiştir. Bu randevu daha sonra [7] ile silinebilir.
Temel işlevler açıklanmıştır. Bunlar basittir. Açıklanmayanlar ise önceki bir görünüme dönmek için kullanılan gezinme işlevleridir. Son olarak dil yönetimine değinelim:
![]() |
- [1]'te Fransızca'dan İngilizce'ye geçilir;
2

- 'den [2]'e, görünüm İngilizceye geçer, takvim de dahil;
3.4. Angular projesinin yapılandırılması
Angular istemcimizi adım adım oluşturacağız. IDE Webstorm'u kullanıyoruz.
Boş bir klasör [rdvmedecins-angular-v1] oluşturalım ve Webstorm ile açalım:
![]() |
- [1] içinde bir klasör açıyoruz;
- [2]'te, oluşturduğumuz klasörü seçiyoruz;
- [3]'te boş bir Webstorm projesi elde ediyoruz;
![]() |
- [4]'te, proje yapılandırması [File / Settings] seçeneği ile yapılır;
- [5] ve [6] dosyalarında, yazım denetimini yöneten [Spelling] özelliğini yapılandırıyoruz. Varsayılan olarak bu özellik etkindir. İndirilen yazılım İngilizce olduğu için, programlara eklediğimiz Fransızca yorumlar olası yazım hataları olarak işaretlenecektir. Bu nedenle [7] yazım denetimini devre dışı bırakıyoruz;
![]() |
- [8]'te yeni bir dosya oluşturuyoruz;
- [9]'te, uygulamayı JSON sözdizimiyle açıklayan [package.json] dosyasını oluşturmayı seçiyoruz;
- [10]'te, oluşturulan dosyayı [11]'te gösterildiği gibi değiştiririz;
- [12]'te, bu dosyayı hem [package.json] hem de [bower.json]'e kaydediyoruz;
![]() |
- [13]'te proje yeniden yapılandırılır;
![]() |
- [14] dosyasına, ihtiyacımız olan JavaScript kütüphanelerini tanımlamamızı sağlayacak [Javascript / Bower] özelliğini yapılandırın;
- [15]'te, az önce oluşturduğumuz [bower.json] dosyasını belirtin;
![]() |
- [16]'te bir JavaScript kütüphanesi ekleyelim;
- [17]'te indirilebilir tüm JavaScript kütüphaneleri görüntülenir;
- [18]'te, [17] listesini filtrelemek için bir kriter belirleyebiliriz. Burada, [Angular JS] kütüphanesini istediğimizi belirtiriz;
- [19] sayfasında kütüphanenin özellikleri görüntülenir. Burada Angular'ın 1.2.18 sürümünün indirileceğini görüyoruz;
- [20]'te kütüphane indiriliyor;
![]() |
- [21]'te, indirildiğini görüyoruz;
- [22]'te indirilen sürüm görülebilir. Yani aslında 1.2.19 sürümüdür;
- [23]'te mevcut en son sürümü görüyoruz;
![]() |
- [24]'te, önceki adımlarla aynı şekilde, aşağıdaki kütüphaneler indirilir:
"user:password" dizesini Base64 ile kodlamak için; | ||
takvimi uluslararası hale getirmek için | ||
uygulamanın dahili URL'lerini doğru denetleyiciye ve görünüme yönlendirmek için; | ||
görünümlerin uluslararasılaştırılmasını sağlar. Bu, Angular'dan bağımsız bir projedir. Burada iki dil kullanılacaktır: Fransızca ve İngilizce; | ||
Bootstrap ile uyumlu görsel bileşenler sağlar. Burada takvimi kullanılacaktır; | ||
CSS Bootstrap çerçeve. Görünümleri oluşturmak için kullanılacak; | ||
"tablo" türü bir görsel bileşen sağlar. Ekran boyutuna uyum sağlayabilmesi açısından "duyarlı"dır; | ||
"açılır liste" türü bir bileşen sağlar; |
![]() |
- [25]'te, indirilen kütüphaneler [bower_components] klasörüne kurulmuştur;
- [26]'te, JQuery kütüphanesinin indirildiği görülüyor. Bunun nedeni, Bootstrap'ın bu kütüphaneyi kullanmasıdır. Bir projenin JavaScript bağımlılıklarını yükleme sistemi, Java dünyasındaki Maven sistemine benzerdir: indirilen bir kütüphanenin kendi bağımlılıkları varsa, bunlar otomatik olarak indirilir;
[bower.json] dosyası güncellendi:
İndirilen tüm bağımlılıklar dosyaya kaydedilmiştir.
3.5. Angular istemcisinin başlangıç sayfası
Angular istemcisinin başlangıç sayfasının ilk sürümünü oluşturuyoruz:
![]() |
- [1] ve [2] adlarıyla, HTML adlı bir dosya oluşturuyoruz; [app-01], [3] ve [4];
[app-01.html] dosyası bir süreliğine ana sayfamız olacak. Bu dosyada, uygulamanın ihtiyaç duyduğu CSS ve JS dosyalarının içe aktarılmasını yapılandıracağız:
<!DOCTYPE html>
<html>
<head>
<title>RdvMedecins</title>
<!-- META -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Angular client for RdvMedecins">
<meta name="author" content="Serge Tahé">
<!-- CSS -->
<link href="bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="bower_components/bootstrap/dist/css/bootstrap-theme.min.css" rel="stylesheet"/>
<link href="bower_components/bootstrap-select/bootstrap-select.min.css" rel="stylesheet"/>
<link href="bower_components/footable/css/footable.core.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container">
<h1>Rdvmedecins - v1</h1>
</div>
<!-- Bootstrap çekirdeği JavaScript ================================================== -->
<script type="text/javascript" src="bower_components/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bower_components/bootstrap-select/bootstrap-select.min.js"></script>
<script type="text/javascript" src="bower_components/footable/dist/footable.min.js"></script>
<!-- AngularJS -->
<script type="text/javascript" src="bower_components/angular/angular.min.js"></script>
<script type="text/javascript" src="bower_components/angular-ui-bootstrap-bower/ui-bootstrap-tpls.min.js"></script>
<script type="text/javascript" src="bower_components/angular-route/angular-route.min.js"></script>
<script type="text/javascript" src="bower_components/angular-translate/angular-translate.min.js"></script>
<script type="text/javascript" src="bower_components/angular-base64/angular-base64.min.js"></script>
</body>
</html>
- 11-12. satırlar: Bootstrap için CSS dosyaları;
- 13. satır: [boostrap-select] bileşeni için CSS dosyası;
- 14. satır: [footable] bileşeni için CSS dosyası;
- 21-24. satırlar: Bootstrap bileşenleri için JS dosyaları;
- 21. satır: Bootstrap bileşenleri, JQuery tarafından desteklenmektedir;
- 22. satır: Bootstrap'ın JS dosyası;
- satır 23: [boostrap-select] bileşeni için JS dosyası;
- 24. satır: [footable] bileşeni için JS dosyası;
- 26-30. satırlar: Angular ve buna bağlı projeler için JS dosyaları;
- 26. satır: Angular için JS dosyası. Bu kütüphane kullanılıyorsa, JQuery dosyasından sonra yüklenmelidir;
- 27. satır: [angular-ui-bootstrap] projesine ait JS dosyası;
- 28. satır: [angular-route] yönlendiricisinin JS dosyası;
- 29. satır: Angular uygulamalarının uluslararasılaştırma modülüne ait JS dosyası;
- 30. satır: [angular-base64] modülüne ait JS dosyası;
[app-01.html] dosyasının geçerliliği şu şekilde kontrol edilebilir:
![]() |
- [1] dosyasında kod denetimi talep edilir;
- [2] dosyasında, her şey yolunda olduğunda elde edilen sonuç;
Kodun çalıştırılmasından önce bu sistematik inceleme yapılması tavsiye edilir. Burada bu inceleme, CSS ve JS dosyalarındaki tüm referans hatalarını tespit etmeyi sağlar. Bir yol yanlışsa, kod denetleyicisi bunu bildirecektir.
- [3]'te, sayfa bir hata ayıklayıcı aracılığıyla tarayıcıya yüklenebilir. Tarayıcıda şu sonuç elde edilir:
![]() |
- [4]'te, [app-01.html] sayfası, burada 63342 numaralı bağlantı noktasında çalışan WebStorm'un dahili bir sunucusu tarafından sunulmuştur;
- [5]'te ise hata ayıklayıcı konsolu yer alır. Herhangi bir hata oluşmuş olsaydı, burada görünürdü. Ayrıca, JavaScript'teki [console.log(expression)] komutunun ürettiği ekran çıktıları da buraya gönderilir. Bu özelliği sıklıkla kullanacağız;
Hata ayıklama modu, WebStorm'da sayfayı düzenlemenize ve sayfayı yeniden yüklemeye gerek kalmadan bu değişikliklerin sonuçlarını tarayıcıda görmenize olanak tanır. Dolayısıyla, aşağıdaki 3. satırı eklersek:
<div class="container">
<h1>Rdvmedecins - v1</h1>
<h2>Version 1</h2>
</div>
ve tarayıcıya geri dönersek, sayfanın değiştiğini görürüz:
![]() |
3.6. Bootstrap'ı Keşfetmek
Şimdi uygulamada kullanılan Bootstrap özelliklerinden bazılarını örneklerle göstereceğiz. Bu çerçeve hakkında, internette bulduğum kodları kopyalayıp yapıştırarak edindiğim sınırlı bir bilgim var. Anladığımı düşündüğüm CSS sınıflarının rolünü açıklayacağım. Diğerleri hakkında yorum yapmayacağım.
3.6.1. Örnek 1
Angular’da dışarıdan bilgi alan işlemler asenkron olarak gerçekleştirilir. Bu, işlemin başlatıldıktan sonra kullanıcının etkileşime devam edebileceği görünüme anında geri dönüldüğü anlamına gelir. İşlemin tamamlandığı, bir olay aracılığıyla uygulamaya bildirilir. Bu olay, JS işlevi tarafından işlenir; bu işlev, mevcut görünümü zenginleştirebilir veya değiştirebilir. İşlemin uzun sürmesi muhtemel ise, kullanıcıya işlemi iptal etme seçeneği sunmak yararlıdır. Bunu kullanıcıya her zaman sunacağız. Bunun için bir Bootstrap başlık çubuğu kullanacağız:

Bu sonucu elde etmek için, [app-01.html] dosyasını [app-02.html] olarak kopyalayıp aşağıdaki satırları değiştiriyoruz:
<div class="container">
<h1>Rdvmedecins - v1</h1>
<div class="alert alert-warning">
<h1>Opération en cours. Veuillez patienter...
<button class="btn btn-primary pull-right">Annuler</button>
<img src="assets/images/waiting.gif" alt=""/>
</h1>
</div>
</div>
- 1. satır: CSS [container] sınıfı, tarayıcının içinde bir görüntüleme alanı tanımlar;
- 3. satır: CSS ve [alert] sınıfları renkli bir alan görüntüler. [alert-warning] sınıfı önceden tanımlanmış bir renk kullanır;
- 5. satır: [btn] sınıfı bir düğmeyi giydirir. [btn-primary] sınıfı ona belirli bir renk verir. [pull-right] sınıfı ise onu uyarı şeridinin sağına yerleştirir;
- 6. satır: bir bekleme animasyonu;
3.6.2. Örnek 2
Uygulamanın farklı görünümleri ortak bir başlığa sahip olacaktır:

Bu sonucu elde etmek için, [app-01.html]'i [app-03.html]'te kopyalayıp aşağıdaki satırları değiştiriyoruz:
<div class="container">
<h1>Rdvmedecins - v1</h1>
<!-- Bootstrap Jumbotron -->
<div class="jumbotron">
<div class="row">
<div class="col-md-2">
<img src="assets/images/caduceus.jpg" alt="RvMedecins"/>
</div>
<div class="col-md-10">
<h1>Les Médecins associés</h1>
</div>
</div>
</div>
</div>
- renkli alan, 4. satırdaki [jumbotron] sınıfı ile elde edilir;
- 5. satır: [row] sınıfı, 12 sütunlu bir satır tanımlar;
- 6. satır: [col-md-2] sınıfı, satırda iki sütunlu bir alan tanımlar;
- 7. satır: Bu iki sütuna bir resim yerleştirilir;
- 9-11. satırlar: Diğer 10 sütuna metin yerleştirilir;
3.6.3. Örnek 3
Görünümlerde üstte bir kontrol çubuğu bulunur. Burada kontrol seçenekleri, bağlantılar veya düğmeler yer alır. Ayrıca form öğeleri de bulunur. Örneğin:
![]() |
Bu sonucu elde etmek için, [app-01.html]'i [app-04.html]'te çoğaltıyoruz ve aşağıdaki satırları değiştiriyoruz:
<div class="container">
<h1>Rdvmedecins - v1</h1>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">RdvMedecins</a>
</div>
<div class="navbar-collapse collapse">
<form class="navbar-form navbar-right">
<!-- hata ayıklama modu -->
<label style="width: 100px">
<input type="checkbox">
<span style="color: white">Debug</span>
</label>
<!-- giriş formu -->
<div class="form-group">
<input type="text" class="form-control" placeholder="Temps d'attente"
style="width: 150px"/>
<input type="text" class="form-control" placeholder="URL du service web"
style="width: 200px"/>
<input type="text" class="form-control" placeholder="Login"
style="width: 100px"/>
<input type="password" class="form-control" placeholder="Mot de passe"
style="width: 100px"/>
</div>
<button class="btn btn-success">
Connexion
</button>
</form>
</div>
<button class="btn btn-success">
Connexion
</button>
</form>
</div>
</div>
</div>
</div>
- 4. satır: [navbar] sınıfı, gezinme çubuğunun stilini belirler. [navbar-inverse] sınıfı, çubuğa siyah arka plan rengi verir. [navbar-fixed-top] sınıfı, tarayıcıda görüntülenen sayfayı "kaydırdığımızda" gezinme çubuğunun ekranın üst kısmında kalmasını sağlar;
- 6-14. satırlar: [1] alanını tanımlar. Bu, genellikle benim anlamadığım bir dizi sınıftır. Bileşeni olduğu gibi kullanıyorum;
- 15. satır: komut çubuğunun “duyarlı” bir alanını tanımlar. Akıllı telefonlarda bu alan bir menü alanının içinde kaybolur;
- satır 16: [navbar-form] sınıfı, komut çubuğundaki bir formu biçimlendirir. [navbar-right] sınıfı ise bu formu komut çubuğunun sağına yerleştirir;
- 23-32. satırlar: 17. satırdaki [3] formundaki dört giriş alanı. Bunlar, bir formun öğelerini biçimlendiren [form-group] sınıfının içindedir ve her biri [form-control] sınıfına sahiptir;
- 33. satır: Daha önce karşılaştığımız [btn] sınıfı, ona yeşil rengini veren [btn-success] sınıfıyla zenginleştirilmiştir;
3.6.4. Örnek 4
Kontrol çubuğu, açılır menü sayesinde dil değiştirmeye olanak sağlayacaktır:

Bu sonucu elde etmek için, [app-01.html] sınıfını [app-05.html] olarak kopyalayıp, kontrol çubuğuna aşağıdaki satırları ekliyoruz:
<button class="btn btn-success">
Connexion
</button>
<!-- diller -->
<div class="btn-group">
<button type="button" class="btn btn-danger">
Langues
</button>
<button type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">
<li>
<a href="">Français</a>
</li>
<li>
<a href="">English</a>
</li>
</ul>
</div>
</form>
Eklenen satırlar 4-21 numaralı satırlardır.
- 5. satır: [btn-group] sınıfı bir düğme grubunu biçimlendirir. 6. ve 9. satırlarda iki tane vardır;
- 6-8. satırlar: İlk düğme, açılır listenin etiketini tanımlar. [btn-danger] sınıfı, bu düğmeye kırmızı renk verir;
- 9-12. satırlar: İkinci düğme, açılır listenin düğmesidir. İlk düğmenin yanına yerleştirilmiştir, bu da tek bir bileşen izlenimi yaratır;
- 10. satır: Düğmenin bir açılır liste olduğunu gösteren aşağı okunu görüntüler;
- 11. satır: ekran okuyucular için;
- 13-20. satırlar: açılır menüdeki öğeler, sıralanmamış bir listenin öğeleridir;
3.6.5. Örnek 5
Bir formu göndermek veya gezinmek için, kullanıcı kontrol çubuğunda aşağıdaki gibi seçenekler veya düğmeler bulacaktır:
![]() |
[1]'te menü seçenekleri eklenmiştir. Bu sonucu elde etmek için, [app-01.html] dosyasını [app-06.html]'te kopyalayıp aşağıdaki satırları ekliyoruz:
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
...
</div>
<!-- menü seçenekleri -->
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active">
<a href="">
<span>Home</span>
</a>
</li>
<li class="active">
<a href="">
<span>Agenda</span>
</a>
</li>
<li class="active">
<a href="">
<span>Valider</span>
</a>
</li>
<li class="active">
<a href="">
<span>Annuler</span>
</a>
</li>
</ul>
<!-- sağdaki düğmeler -->
<form class="navbar-form navbar-right" role="form">
...
</form>
</div>
</div>
</div>
</div>
- Menü seçenekleri 8-29. satırlarda oluşturulur. Bunlar yine bir <ul> listesinin öğeleridir. [active] sınıfı, metni parlak hale getirerek seçeneğin tıklanabilir olduğunu gösterir.
3.6.6. Örnek 6
Doktorları ve müşterileri aşağıdaki gibi açılır listelerde sunacağız:
![]() |
Kullanılan açılır liste, Bootstrap'ın yerel bir bileşeni değildir. Bu, [bootstrap-select] bileşenidir (http://silviomoreto.github.io/bootstrap-select/). Bu sonucu elde etmek için, [app-01.html] dosyasını [app-07.html] olarak kopyalayıp aşağıdaki satırları ekliyoruz:
<!DOCTYPE html>
<html>
<head>
...
<link href="bower_components/bootstrap-select/bootstrap-select.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container">
<h1>Rdvmedecins - v1</h1>
<h2><label for="medecins">Médecins</label></h2>
<select id="medecins" data-style="btn btn-primary" class="selectpicker">
<option value="1">Mme Marie PELISSIER</option>
<option value="1">Mr Jacques BROMARD</option>
<option value="1">Mr Philippe JANDOT</option>
<option value="1">Mme Justine JACQUEMOT</option>
</select>
</div>
<!-- Bootstrap çekirdeği JavaScript ================================================== -->
...
<script type="text/javascript" src="bower_components/bootstrap-select/bootstrap-select.min.js"></script>
<!-- yerel komut dosyası -->
<script>
$('.selectpicker').selectpicker();
</script>
</body>
</html>
- 5. satır: [bootstrap-select] stil sayfasını içe aktarmak gerekir;
- 13. satır: [data-style] özniteliği, [bootstrap-select] tarafından kullanılır. Bu öznitelik, açılır listeye stil vermek için kullanılır. Burada, ona mavi bir düğme şekli verilir ([btn-primary]);
- 13. satır: [class] özniteliği 23. satırda kullanılır. Herhangi bir değer olabilir;
- satır 14-17: açılır listenin öğeleri. Burada klasik HTML etiketleri bulunmaktadır;
- 22. satır: JS'i [bootstrap-select]'ten içe aktarmak gerekir;
- 24-26. satırlar: sayfanın yüklenmesi tamamlandığında çalıştırılan bir JS komut dosyası;
- 25. satır: bir JQuery komutu. [selectpicker] (selectpicker()) yöntemi, [selectpicker] sınıfına sahip tüm öğelere ($('.selectpicker')) uygulanır. Bunlardan sadece biri vardır: 13. satırdaki <select> etiketi. [selectpicker] yöntemi, 22. satırda referans verilen JS dosyasından gelir;
3.6.7. Örnek 7
Bir doktorun randevu takvimini görüntülemek için, JS [footable] kütüphanesi tarafından sağlanan 'duyarlı' bir tablo kullanacağız:
![]() |
- [1]'te: normal görüntülemeli tablo;
- [2]'te: tarayıcı penceresinin boyutu küçültüldüğünde tablo. [Action] sütunu otomatik olarak bir sonraki satıra geçer. Buna "duyarlı" veya kısaca uyarlanabilir bileşen denir.
[app-01.html]'i [app-08.html]'e kopyalıyoruz ve aşağıdaki satırları ekliyoruz:
...
<link href="bower_components/footable/css/footable.core.min.css" rel="stylesheet"/>
<link href="assets/css/rdvmedecins.css" rel="stylesheet"/>
...
<div class="container">
<h1>Rdvmedecins - v1</h1>
<div class="row alert alert-warning">
<div class="col-md-6">
<table id="creneaux" class="table">
<thead>
<tr>
<th data-toggle="true">
<span>Créneau horaire</span>
</th>
<th>
<span>Client</span>
</th>
<th data-hide="phone">
<span>Action</span>
</th>
</thead>
<tbody>
<tr>
<td>
<span class='status-metro status-active'>
9h00-9h20
</span>
</td>
<td>
<span></span>
</td>
<td>
<a href="" class="status-metro status-active">
Réserver
</a>
</td>
</tr>
<tr>
<td>
<span class='status-metro status-suspended'>
9h20-9h40
</span>
</td>
<td>
<span>Mme Paule MARTIN</span>
</td>
<td>
<a href="" class="status-metro status-suspended">
Supprimer
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
...
<script src="bower_components/footable/dist/footable.min.js" type="text/javascript"></script>
- 2. ve 60. satırlar [app-01.html] dosyasında zaten mevcuttur. Bunlar, [footable] kütüphanesi tarafından sağlanan CSS ve JS dosyalarıdır;
- 3. satır, aşağıdaki CSS dosyasına atıfta bulunmaktadır:
@CHARSET "UTF-8";
#th aralıkları {
text-align: center;
}
#td aralıkları {
text-align: center;
font-weight: bold;
}
.status-metro {
display: inline-block;
padding: 2px 5px;
color:#fff;
}
.status-metro.status-active {
background: #43c83c;
}
.status-metro.status-suspended {
background: #fa3031;
}
[status-*] stilleri, kütüphane sitesinde bulunan [footable] tablosunun kullanım örneğinden alınmıştır.
- 8. satır: tabloyu bir satıra [row] ve renkli bir çerçeveye [alert alert-warning] yerleştirir;
- 9. satır: Tablo 6 sütun [col-md-6] kaplayacaktır;
- 10. satır: HTML tablosu, Bootstrap [class='table'] ile biçimlendirilmiştir;
- 13. satır: [data-toggle] özniteliği, satırı açıp kapatan [+/-] simgesini barındıran sütunu belirtir;
- 19. satır: [data-hide='phone'] özniteliği, ekran boyutu bir telefon ekranı boyutunda ise sütunun gizlenmesi gerektiğini belirtir. 'tablet' değeri de kullanılabilir;
3.6.8. Örnek 8
Kullanıcıya yardımcı olmak için, görünümlerin ana bileşenlerinin etrafına yardım balonları (tooltip) oluşturacağız:
![]() |
Bu sonucu elde etmek için, [app-01.html] öğesini [app-09.html] olarak kopyalayıp aşağıdaki satırları ekliyoruz:
<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
...
</head>
<body>
<div class="container">
<h1>Rdvmedecins - v1</h1>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">RdvMedecins</a>
</div>
<!-- menü seçenekleri -->
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active">
<a href="">
<span tooltip="Retourne à la page d'accueil" tooltip-placement="bottom">Home</span>
</a>
</li>
<li class="active">
<a href="">
<span tooltip="Affiche l'agenda" tooltip-placement="top">Agenda</span>
</a>
</li>
<li class="active">
<a href="">
<span tooltip="Valide le rendez-vous" tooltip-placement="right">Valider</span>
</a>
</li>
<li class="active">
<a href="">
<span tooltip="Annule l'opération en cours" tooltip-placement="left">Annuler</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Bootstrap çekirdeği JavaScript ================================================== -->
<...
<script type="text/javascript" src="bower_components/angular-ui-bootstrap-bower/ui-bootstrap-tpls.min.js"></script>
<!-- yerel komut dosyası -->
<script>
// --------------------- Angular modülü
angular.module("rdvmedecins", ['ui.bootstrap']);
</script>
</body>
</html>
Yardım balonları, [angular-ui-bootstrap] kütüphanesi tarafından sağlanır; bu kütüphane ise [angular] kütüphanesini kullanır. 50. satırda [angular-ui-bootstrap] kütüphanesi içe aktarılır. [angular-ui-bootstrap] kütüphanesinin bileşenlerini kullanmak için bir Angular modülü oluşturmamız gerekir. Bu işlem 52-55. satırlarda gerçekleştirilir. Bu satırlar, [rdvmedecins] adlı bir Angular modülünü tanımlar (1. parametre). Bir Angular modülü, diğer Angular modüllerini kullanabilir. Buna modül bağımlılıkları denir. Bunlar, [angular.module] işlevinin ikinci parametresi olarak bir dizide sağlanır. Burada, [ui.bootstrap] adlı modül, [angular-ui-bootstrap] kütüphanesi tarafından sağlanmaktadır. Yardım balonlarını bize sağlayacak olan modül budur.
- satırda bir Angular modülü tanımlanmaktadır. Varsayılan olarak, bunun sayfa üzerinde herhangi bir etkisi yoktur. Sayfanın bir Angular modülüne bağlanarak Angular tarafından yönetilmesi gerektiği belirtilir. Bu işlem, 2. satırda gerçekleştirilmiştir. [ng-app='rdvmedecins'] özniteliği, sayfayı 54. satırda oluşturulan modüle bağlar. Sayfa daha sonra Angular tarafından analiz edilecektir. [tooltip] öznitelikleri, [ui.bootstrap] modülü tarafından tespit edilecek ve işlenecektir.
Yardım balonunun sözdizimi şöyledir:
<span tooltip="Retourne à la page d'accueil" tooltip-placement="bottom">Home</span>
Yukarıda, [Home] metnine bir yardım balonu eklenmiştir:
- [tooltip]: yardım balonunun metnini tanımlar;
- [tooltip-placement]: konumunu (alt, üst, sol, sağ) tanımlar;
Angular JS, HTML dilinde halihazırda mevcut olan etiketlere veya özniteliklere yenilerini eklemeye olanak tanır. HTML dilinin bu genişletmesi, Angular direktifleri aracılığıyla gerçekleştirilir. Burada [tooltip] ve [tooltip-placement] öznitelikleri, [angular-ui-bootstrap] tarafından oluşturulan özniteliklerdir.
3.6.9. Örnek 9
Kullanıcının randevu gününü seçmesine yardımcı olmak için ona bir takvim sunacağız:

Yardım balonlarında olduğu gibi, bu takvim de [angular-ui-bootstrap] kütüphanesi tarafından sağlanmaktadır. Bu sonucu elde etmek için, [app-01.html] dosyasını [app-10.html] dosyasına kopyalayıp aşağıdaki satırları ekliyoruz:
<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
...
<body>
<div class="container">
<h1>Rdvmedecins - v1</h1>
<div>
<pre>Date <em>{{jour | date:'fullDate'}}</em></pre>
<div class="row">
<div class="col-md-2">
<h4>Calendrier</h4>
<div style="display:inline-block; min-height:290px;">
<datepicker ng-model="jour" show-weeks="true" class="well"></datepicker>
</div>
</div>
</div>
</div>
</div>
</div>
...
<!-- yerel komut dosyası -->
<script>
// --------------------- Angular modülü
angular.module("rdvmedecins", ['ui.bootstrap'])
</script>
</body>
</html>
Daha önce olduğu gibi, sayfa bir Angular modülüne bağlanmıştır (2. ve 28. satırlar). Takvim, [angular-ui-bootstrap] kütüphanesi tarafından tanımlanan 16. satırdaki <datepicker> etiketiyle tanımlanmıştır:
- [show-weeks='true']: hafta numaralarını görüntülemek için;
- [class='well']: takvimi köşeleri yuvarlatılmış gri bir alanla çevrelemek için;
- [ng-model='jour']: [ng-*] öznitelikleri Angular öznitelikleridir. [ng-model] özniteliği, görünüm şablonuna yerleştirilecek bir veriyi belirtir. Kullanıcı bir tarihe tıkladığında, bu tarih modelin [jour] değişkenine yerleştirilecektir. Bu değişken 10. satırda kullanılmaktadır. {{ifade}} sözdizimi, model öğelerinden oluşan bir ifadenin değerlendirilmesini sağlar. Burada {{gün}}, modeldeki [jour] değişkeninin değerini gösterecektir. Angular'ın en önemli özelliklerinden biri, görünümün [jour] değişkenindeki değişiklikleri otomatik olarak takip etmesidir. Böylece, kullanıcı tarihleri değiştirdiğinde, bu değişiklikler 10. satırda anında görüntülenecektir. Genel olarak, işleyiş şu şekildedir:
- bir görünüm V, bir modele M eşleştirilir;
- Angular, M modelini izler ve modelinde bir değişiklik olduğunda V görünümünü otomatik olarak günceller;
{{gün|tarih}} sözdizimi bir filtre olarak adlandırılır. Ekrana [jour] değişkeninin değeri değil, [jour] değişkeninin [date] adlı filtreyle filtrelenmiş değeri yansıtılır. Bu filtre, Angular'da önceden tanımlanmıştır. Bu filtre, tarihleri biçimlendirmek için kullanılır. İstenen biçimi belirten parametreleri kabul eder. Dolayısıyla {{gün | tarih:'fullDate'}} ifadesi, tarihin tam biçimini istediğimizi gösterir; burada [Friday, June 20, 2014] kullanılır çünkü takvim varsayılan olarak İngilizcedir. Yakında bu filtrenin uluslararasılaştırılması konusunu ele alacağız.
3.6.10. Sonuç
Kullanacağımız CSS Bootstrap çerçevesinin bileşenlerini tanıttık. Bunlar pasif bileşenlerdi: olayları yönetilmiyordu. Dolayısıyla düğmelere veya bağlantılara tıklamak hiçbir şey yapmazdı. Bu olaylar JavaScript ile yönetilecektir. Bu dili çerçeveler olmadan kullanmak mümkündür, ancak sunucu tarafında olduğu gibi, istemci tarafında da bazı çerçeveler vazgeçilmez hale gelmektedir. Angular JS çerçevesi de bunlardan biridir ve tarayıcı tarafından çalıştırılan JavaScript uygulamalarının geliştirilmesine yeni bir yaklaşım getirir. Şimdi bunu tanıtacağız.
3.7. Angular JS’i Keşfetmek
Şimdi, uygulamada kullanılan Angular JS çerçevesinin bazı özelliklerini örneklerle açıklayacağız. Bunlardan bazılarıyla daha önce tanışmıştık:
- Bir HTML sayfası, kendisine bir modül eklenirse Angular JS tarafından çalıştırılır:
<html ng-app="rdvmedecins">
- Angular, direktifler aracılığıyla yeni etiketler ve yeni öznitelikler HTML oluşturulmasına olanak tanır:
- Angular, filtreler oluşturulmasına olanak tanır:
- Bir V görünümü, bir M modelini görüntüler. Angular, M modelini izler ve modelinde bir değişiklik olduğunda V görünümünü otomatik olarak günceller. M modelindeki bir değişkenin değeri, V görünümünde şu şekilde görüntülenir:
Öncelikle Angular'da Model – Görünüm – Denetleyici Tasarım Deseninin uygulanmasını daha ayrıntılı olarak inceleyeceğiz. Mimari açıdan aralarındaki bağlantıları hatırlayalım:
![]() |
- V1 görünümü, C1 denetleyicisi tarafından oluşturulan M1 modelini görüntüler. Kontrolör, sadece M1 modelini değil, aynı zamanda V1 görünümünün olay işleyicilerini de içerir. 5, 8, 9 döngüsündeyiz:
- [5]: V1 görünümünde bir olay meydana gelir. Bu olay, C1 denetleyicisi tarafından işlenir;
- bu denetleyici [6-7] işini yapar ve ardından M1 ile [8] modellerini oluşturur;
- [9]: V1 görünümü, yeni M1 modelini görüntüler. Daha önce de belirttiğimiz gibi, bu son adım otomatiktir. Diğer çerçevelerde olduğu gibi burada MVC gibi açık bir itme (C1, M1 şablonunu V1'e iter) veya açık bir çekme işlemi yoktur (V1 görünümü, M1 modelini C1'ten alır) yoktur. Geliştiricinin görmediği örtük bir itme işlemi vardır;
- ardından 5, 8, 9 döngüsü devam eder;
3.7.1. Örnek 1: Angular'ın MVC şablonu
Takvim örneğine geri dönelim. Bunu oluşturan yönergeyi daha önce görmüştük:
<datepicker ng-model="jour" show-weeks="true" class="well"></datepicker>
Bu direktif, yukarıda sunulanların dışında başka öznitelikler de kabul eder; örneğin, takvimde seçilebilecek en erken tarihi belirleyen [min-date] özniteliği gibi. Bu bizim için yararlı olacaktır. Kullanıcı bir randevu tarihi seçtiğinde, bu tarih günümüz tarihine eşit veya ondan daha geç olmalıdır. O halde şunu yazacağız:
<datepicker ng-model="jour" ... min-date="dateMin"></datepicker>
burada [dateMin], günün tarihini değer olarak alacak olan sayfa şablonundaki bir değişken olacaktır. Bu, aşağıdaki sayfayı verecektir:
![]() |
- [1]'te, tarih 19 Haziran 2014'tür. İmleç, 19 Haziran'ın seçilebileceğini gösterir;
- [2]'te ise imleç, 18 Haziran'ın seçilemeyeceğini gösteriyor;
[app-10.html] dosyasını [app-11.html] dosyasına kopyalıyoruz ve aşağıdaki değişiklikleri yapıyoruz:
<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
...
</head>
<body ng-controller="rdvMedecinsCtrl">
<div class="container">
<h1>Rdvmedecins - v1</h1>
<div>
<pre>Date <em>{{jour | date:'fullDate' }}</em></pre>
<div class="row">
<div class="col-md-2">
<h4>Calendrier</h4>
<div style="display:inline-block; min-height:290px;">
<datepicker ng-model="jour" show-weeks="true" class="well" min-date="minDate"></datepicker>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap çekirdeği JavaScript ================================================== -->
...
<!-- yerel komut dosyası -->
<script>
// --------------------- Angular modülü
angular.module("rdvmedecins", ['ui.bootstrap']);
// denetleyici
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope',
function ($scope) {
// en erken tarih
$scope.minDate = new Date();
}]);
</script>
</body>
</html>
Öncelikle 26-37. satırlardaki yerel komut dosyasını inceleyelim:
- 28. satır: Takvimi sağlayan [ui.bootstrap] modülüne bağımlı olan [rdvmedecins] modülünün oluşturulması;
- 30-35. satırlar: Bir denetleyicinin oluşturulması. Sayfa şablonumuzu barındıracak olan budur. Burada bir olay işleyicisi olmayacaktır;
- satır 30-31: [rdvMedecinsCtrl] denetleyicisi, [rdvmedecins] modülüne aittir. Bir modüle istediğiniz kadar denetleyici ekleyebilirsiniz. Uygulamamızda şunlar olacaktır:
- bir uygulama yönetim modülü;
- her görünüm için bir denetleyici;
- [controller] işlevinin ikinci parametresi, ['O1', 'O2', ..., 'On', function(O1, O2, ..., On)] biçiminde bir dizidir. Son parametre ise denetleyiciyi uygulayan işlevdir. Bu işlevin parametreleri, Angular JS'in işlevine sağlayacağı nesnelerdir.
Angular uygulamasının mimarisine geri dönelim:
![]() |
Yukarıda, C1 denetleyicisi, V1 görünümünün tüm olay işleyicilerini ve bu görünümün M1 modelini içerir. Olay işleyicilerinin görevlerini yerine getirebilmeleri için bir veya daha fazla [6] hizmetine ihtiyaçları olabilir. Bunların tümü, denetleyicinin oluşturma işlevine parametre olarak aktarılır:
Si hizmetleri singletonlardır. Angular bunları tek bir örnek olarak oluşturur. Bu hizmetler bir Si adıyla tanımlanır. Neden yukarıdaki tabloda iki kez yer alıyorlar? Çalışma sırasında, JS komut dosyaları minifiye edilir. Bu minifikasyon sürecinde, yukarıdaki tablo şu hale gelir:
Parametreler adlarını yitirir. Oysa bunlar hizmetlerin adlarıdır. Dolayısıyla bu adları korumak önemlidir. Bu nedenle, fonksiyonun öncesinde parametreler olarak karakter dizeleri şeklinde aktarılırlar. Karakter dizeleri, minifikasyon sürecinde değiştirilmez. Angular, yeni diziyle denetleyiciyi oluştururken a1'i S1 ile, a2'yi S2 ile değiştirecektir, ... Dolayısıyla parametrelerin sırası önemlidir. Bu sıra, fonksiyon tanımının önündeki hizmetlerin sırasına uymalıdır.
[rdvMedecinsCtrl] denetleyicisinin tanımına geri dönelim:
// denetleyici
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope',
function ($scope) {
// minimum tarih
$scope.minDate = new Date();
}]);
- 3-4. satırlar: Denetleyiciye enjekte edilen tek nesne, $scope nesnesidir. Bu, denetleyiciyle ilişkili görünümlerin M modelini temsil eden önceden tanımlanmış bir nesnedir. Bir görünümün modelini zenginleştirmek için, $scope nesnesine alanlar eklemek yeterlidir;
- bu işlem 6. satırda gerçekleştirilmiştir. Günün tarihi değerini içeren [minDate] alanı oluşturulur;
V görünümü, bu M modelini şu şekilde kullanır:
<body ng-controller="rdvMedecinsCtrl">
<div class="container">
...
<div style="display:inline-block; min-height:290px;">
<datepicker ng-model="jour" show-weeks="true" class="well" min-date="minDate"></datepicker>
</div>
...
</div>
...
- 1. satır: Sayfanın gövdesi, [ng-controller] özniteliği sayesinde [rdvMedecinsCtrl] denetleyicisiyle ilişkilendirilir. Bu, <body> etiketinin içindeki her şeyin, olaylarını yönetmek ve M modelini almak için [rdvMedecinsCtrl] denetleyicisini kullanacağı anlamına gelir. Bir HTML sayfası, birbirine iç içe geçmiş veya iç içe geçmemiş birden fazla denetleyiciye bağlı olabilir:
Yukarıda:
- [div1]'in içeriği (1-10. satırlar), c1 denetleyicisi tarafından yönetilen M1 şablonunu görüntüler. Bu alandaki etiketler, c1 denetleyicisinin olay işleyicilerine başvurabilir;
- [div11] dosyasının içeriği (3-4. satırlar), c11 denetleyicisi tarafından yönetilen M11 şablonunu ve aynı zamanda M1 şablonunu da görüntüler. Şablonlar arasında miras ilişkisi vardır. Bu bölgedeki etiketler, hem c11 denetleyicisinin olay yöneticilerine hem de c1 denetleyicisinin olay yöneticilerine başvurabilir. Ancak ne c12 denetleyicisinin M12 şablonuna ne de bu denetleyicinin olay yöneticilerine başvurabilirler. Nitekim c12 denetleyicisi, 3-5. satırlar arasında tanınmamaktadır;
- 7-9. satırlar: Daha önce yapılan mantığa benzer bir mantık izlenebilir;
Takvim koduna geri dönelim:
<datepicker ng-model="jour" show-weeks="true" class="well" min-date="minDate"></datepicker>
[min-date] özniteliği, şablondaki [minDate] değeriyle başlatılır. Dolaylı olarak [$scope.minDate]. Alan her zaman $scope nesnesinde aranır.
3.7.2. Örnek 2: Tarihlerin yerelleştirilmesi
Şu an için takvim bize pek bir fayda sağlamıyor, çünkü bu bir İngiliz takvimi. Takvimi yerelleştirmek mümkündür:
![]() |
- [1]'te Fransızca bir takvim elde ederiz;
- [2] ile takvimi İngilizceye çevirebiliriz;
- [3] ile İngilizce takvime;
[app-11.html] sayfasını [app-12.html] olarak kopyalayıp, sonrakini şu şekilde değiştiriyoruz:
<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
...
</head>
<body ng-controller="rdvMedecinsCtrl">
<div class="container">
<h1>Rdvmedecins - v1</h1>
<pre>Date <em>{{jour | date:'fullDate' }}</em></pre>
<div class="row">
<!-- takvim-->
<div class="col-md-4">
<h4>Calendrier</h4>
<div style="display:inline-block; min-height:290px;">
<datepicker ng-model="jour" show-weeks="true" class="well" min-date="minDate"></datepicker>
</div>
</div>
<!-- diller -->
<div class="col-md-2">
<div class="btn-group" dropdown is-open="isopen">
<button type="button" class="btn btn-primary dropdown-toggle" style="margin-top: 30px">
Langues<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="" ng-click="setLang('fr')">Français</a></li>
<li><a href="" ng-click="setLang('en')">English</a></li>
</ul>
</div>
</div>
</div>
</div>
...
<script type="text/javascript" src="rdvmedecins.js"></script>
</body>
</html>
Değişiklikler çok az. Sadece 21-31. satırlara dil açılır menüsü eklenmiş. İlk kez, 27-28. satırlarda bir olay işleyicisiyle karşılaşıyoruz:
- 27. satır: [ng-click] özniteliği, bu özniteliğe sahip öğeye tıklandığında çalıştırılacak olay işleyicisini belirten bir Angular özniteliğidir. Burada, [$scope.setLang('fr')] işlevi çalıştırılacaktır. Bu işlev, takvimi Fransızca olarak ayarlayacaktır;
- 28. satır: Burada takvimi İngilizceye çeviriyoruz;
- 35. satır: Denetleyicinin JavaScript kodu oldukça uzun olduğu için, bunu [rdvmedecins.js] adlı bir dosyaya yerleştiriyoruz;
Angular, [ngLocale] adlı bir modül ile görünümlerin yerelleştirilmesini yönetir. Dolayısıyla, [rdvmedecins] modülümüzün tanımı şu şekilde olacaktır:
// --------------------- Angular modülü
angular.module("rdvmedecins", ['ui.bootstrap', 'ngLocale']);
- satırda, bağımlılıkları unutmamak gerekir çünkü Angular bazen hata mesajlarında yeterince net olmayabilir. Bir bağımlılığın unutulması bu nedenle tespit edilmesi özellikle zordur. Burada [ngLocale] modülüne yeni bir bağımlılık ekledik.
Varsayılan olarak Angular, yalnızca yerel varyantları olan tarih, sayı vb. öğelerin yerelleştirilmesini yönetir. Metinlerin uluslararasılaştırılmasını yönetmez. Bunun için [angular-translate] kütüphanesini kullanacağız. Yerelleştirme yönetimi, [angular-i18n] kütüphanesi tarafından gerçekleştirilir. Bu kütüphane, tarihler, sayılar vb. için mevcut olan varyant sayısı kadar dosya içerir.
![]() |
Fransız takvimi için [angular-locale_fr-fr.js] dosyasını, İngilizce takvim için ise [angular-locale_en-us.js] dosyasını kullanacağız. Örneğin [angular-locale_fr-fr.js] dosyasında neler olduğuna bir göz atalım:
''use strict';
angular.module("ngLocale", [], ["$provide", function($provide) {
var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
$provide.value("$locale", {
"DATETIME_FORMATS": {
"AMPMS": [
"AM",
"PM"
],
"DAY": [
"dimanche",
"lundi",
"mardi",
"mercredi",
"jeudi",
"vendredi",
"samedi"
],
"MONTH": [
"janvier",
"f\u00e9vrier",
"mars",
"avril",
"mai",
"juin",
"juillet",
"ao\u00fbt",
"septembre",
"octobre",
"novembre",
"d\u00e9cembre"
],
"SHORTDAY": [
"dim.",
"lun.",
"mar.",
"mer.",
"jeu.",
"ven.",
"sam."
],
"SHORTMONTH": [
"janv.",
"f\u00e9vr.",
"mars",
"avr.",
"mai",
"juin",
"juil.",
"ao\u00fbt",
"sept.",
"oct.",
"nov.",
"d\u00e9c."
],
"fullDate": "EEEE d MMMM y",
"longDate": "d MMMM y",
"medium": "d MMM y HH:mm:ss",
"mediumDate": "d MMM y",
"mediumTime": "HH:mm:ss",
"short": "dd/MM/yy HH:mm",
"shortDate": "dd/MM/yy",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "\u20ac",
"DECIMAL_SEP": ",",
"GROUP_SEP": "\u00a0",
"PATTERNS": [
{
"gSize": 3,
"lgSize": 3,
"macFrac": 0,
"maxFrac": 3,
"minFrac": 0,
"minInt": 1,
"negPre": "-",
"negSuf": "",
"posPre": "",
"posSuf": ""
},
{
"gSize": 3,
"lgSize": 3,
"macFrac": 0,
"maxFrac": 2,
"minFrac": 2,
"minInt": 1,
"negPre": "(",
"negSuf": "\u00a0\u00a4)",
"posPre": "",
"posSuf": "\u00a0\u00a4"
}
]
},
"id": "fr-fr",
"pluralCat": function (n) { if (n >= 0 && n <= 2 && n != 2) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;}
});
}]);
Burada, bir Fransız takvimi oluşturmaya yarayan öğeleri görüyoruz:
- 10-18. satırlar: haftanın günleri tablosu;
- 19-32. satırlar: yılın ayları tablosu;
- 33-41. satırlar: haftanın günlerinin kısaltılmış tablosu;
- 42-55. satırlar: yılın aylarının kısaltılmış tablosu;
- 56-63. satırlar: tarih ve saat biçimleri. 62. satırda Fransız tarih biçiminde 'gg/aa/yy' biçimi görülmektedir;
- satır 65-95: sayı biçimlendirmeyle ilgili bilgiler. Bu, burada bizi ilgilendirmiyor;
- 96. satır: dosyanın yerel ayarının 'fr-fr' kimliği (fr-fr: Fransa Fransızcası, fr-ca: Kanada Fransızcası, ...)
[angular-locale_en-us.js] dosyasında da tam olarak aynı şey var, ancak bu sefer USA (en-us) dosyasının İngilizce versiyonu için.
Yukarıdaki kodun okunması pek kolay değil. Dikkatlice okunduğunda, tüm bu kodun 4. satırdaki [$locale] değişkenini tanımladığı anlaşılıyor. Tarihlerin, sayıların, para birimlerinin vb. uluslararası hale getirilmesi, bu değişkenin değerini değiştirerek sağlanır. İlginç bir şekilde, Angular, [$locale] değişkeninin çalışma sırasında değiştirilmesini öngörmemiştir. Bu değişken, istenen yerel ayar dosyasını içe aktararak bir kez ve kalıcı olarak tanımlanır:
<script type="text/javascript" src="bower_components/angular-i18n/angular-locale_fr-fr.js"></script>
İstenen tüm yerel ayar dosyalarını içe aktarmanın bir anlamı yoktur, çünkü gördüğümüz gibi her dosya tek bir şey yapar: [$locale] değişkenini tanımlar. En son içe aktarılan dosya geçerli olur ve bundan sonra yerel ayarı değiştirmenin bir yolu kalmaz.
İnternette bu soruna bir çözüm ararken bir şey bulamadım. Burada bir çözüm öneriyorum: [https://github.com/stahe/angular-ui-bootstrap-datepicker-with-locale-updated-on-the-fly]. Fikir, ihtiyacımız olan farklı yerel ayarları bir sözlüğe yerleştirmektir. Değiştirmemiz gerektiğinde buradan alacağız. [rdvmedecins.js]'in JavaScript kodu şu yapıya sahiptir:
![]() |
200 satırlık yerel ayar tanımını (yukarıdaki 15-215. satırlar) çıkarırsak, kod oldukça basit hale gelir:
- 6. satır: [rdvmedecins] modülünü ve bağımlılıklarını tanımlar;
- 8-10. satırlar: sayfanın [rdvMedecinsCtrl] denetleyicisini tanımlar;
- 9. satır: Denetleyicinin oluşturma işlevi iki parametre alır:
- $scope: görünüm şablonunu oluşturmak için;
- $locale: takvimin yerelleştirilmesini yöneten değişkendir. Dil değiştirildiğinde değiştirilmesi gereken değişken budur;
- 13. satır: Şablondaki [minDate] değişkeni günün tarihi ile başlatılır;
- 15. satır: [locales] sözlüğünü tanımlar. [$scope.locales] yazılmadığını unutmayın. [locales] değişkeni, görünüme sunulan şablonun bir parçası değildir;
- 15-215. satırlar: {'fr':locale-fr-fr, 'en':locale-en-us} sözlüğünü tanımlar. [locale-fr-fr] ve [locale-en-us] değerleri sırasıyla JS, [angular-locale_fr-fr.js] ve [angular-locale_en-us.js] dosyalarından alınır. En zor olanı, bu sözlükteki çok sayıdaki parantezlerde hata yapmamaktır...
- 217. satır: $locale değişkeni, locales['fr'], yani yerel ayarın Fransızca sürümü ile başlatılır. Sadece [$locale=locales['fr']] yazmak mümkün değildir; çünkü bu, $locale'e locales['fr']'in adresini atar. Değerin bir kopyası oluşturulmalıdır. Bu, önceden tanımlanmış [angular.copy] işleviyle yapılabilir;
- 219. satır: Şablondaki [jour] değişkeni günün tarihi ile başlatılır. Bu, takvimin bu tarihte konumlandırılmış olarak görüntülenmesini sağlar;
- 223-230. satırlar: dil değiştirildiğinde çağrılan olay işleyicisini tanımlar. Aşağıdaki sözdizimine dikkat edilmelidir:
[nom_fonction] adında ve [param1, param2, ...] parametrelerini kabul eden bir olay işleyicisini tanımlamak için kullanılır;
Açılır listeden HTML kodunu hatırlayalım:
<!-- diller -->
<div class="col-md-2">
<div class="btn-group" dropdown is-open="isopen">
<button type="button" class="btn btn-primary dropdown-toggle" style="margin-top: 30px">
Langues<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="" ng-click="setLang('fr')">Français</a></li>
<li><a href="" ng-click="setLang('en')">English</a></li>
</ul>
</div>
</div>
- 8. satır: Fransızca seçildiğinde [setLang('fr')] çağrılır;
- 9. satır: İngilizce seçildiğinde [setLang('en')] çağrılır;
- 3. satır: [is-open] özniteliği, açılır menünün açık (true) veya kapalı (false) olmasını kontrol eden bir boole değeridir. Bu öznitelik, görünüm şablonundaki [isopen] değişkeni ile başlatılır;
[rdvmedecins.js] koduna geri dönelim:
- 225. satır: [$locale] değişkeninin değeri, uygun olan [locales] sözlüğünün değeriyle değiştirilir;
- 227. satır: Bir V görünümünün M şablonu değiştiğinde, V görünümünün otomatik olarak yeni şablonla yenileneceği belirtilmiştir. 225. satırda, V görünümü tarafından görüntülenen M modelinin bir parçası olmayan [$locale] değişkeninin değeri değiştirilmiştir. Takvimin yenilenmesi ve yeni yerel ayarlarını kullanması için bu M modelini değiştirmenin bir yolunu bulmak gerekir. Burada, takvim modelindeki [jour] değişkenini değiştiriyoruz. Bu değişkeni, görüntülenen tarihle aynı olan bir tarihe işaret eden yeni bir işaretçi (new) ile başlatıyoruz. [$scope.jour.getTime()], 1 Ocak 1970 ile takvimde görüntülenen tarih arasında geçen milisaniye sayısını ifade eder. Bu sayı ile yeni bir tarih oluşturuyoruz. Elbette aynı tarihi bulacağız ve takvim, görüntülediği tarihte kalmaya devam edecektir. Ancak, aslında bir işaretçi olan [$scope.jour]'in değeri değişmiş olacak ve takvim yenilenecektir;
- 229. satır: Şablondaki [isopen] değişkeninin değerini false olarak ayarlıyoruz. Bu değişken, açılır listenin özniteliklerinden birini kontrol eder:
<div class="btn-group" dropdown is-open="isopen">
<button type="button" class="btn btn-primary dropdown-toggle" style="margin-top: 30px">
Langues<span class="caret"></span>
</button>
...
</div>
Yukarıdaki 1. satırda, [is-open] özniteliği false olarak değişecek ve bu da açılır menüyü kapatacaktır.
3.7.3. Örnek 3: Metinlerin uluslararasılaştırılması
Takvimin yerelleştirilmesine geri dönelim:
![]() |
[3]'te takvimin İngilizce olduğunu görüyoruz, ancak [Calendrier, Langues]'teki metinler İngilizce değil. Varsayılan olarak, Angular mesajların uluslararasılaştırılması için bir araç sunmaz. Burada [angular-translate] kütüphanesini (https://github.com/angular-translate/angular-translate) kullanacağız.
Aşağıdaki örneği geliştireceğiz:
![]() |
- [1], Fransızca görünüm;
- [2], İngilizce görünüm;
Uluslararasılaştırma için gerekli yapılandırmaya bakalım. [rdvmedecins.js] komut dosyası şu şekilde değiştirilir:
// --------------------- Angular modülü
angular.module("rdvmedecins", ['ui.bootstrap', 'ngLocale', 'pascalprecht.translate']);
// i18n yapılandırması
angular.module("rdvmedecins")
.config(['$translateProvider', function ($translateProvider) {
// Fransızca mesajlar
$translateProvider.translations("fr", {
'msg_header': 'Muayenehane<br/>Ortak Doktorlar',
'msg_langues': 'Diller',
'msg_agenda': '{{başlık}} {{ad}} {{soyadı}}'nın {{gün}} tarihli ajandası<br/>,
'msg_calendrier': 'Takvim',
'msg_jour': 'Seçilen gün: ',
'msg_meteo': "Bugün yağmur yağacak..."
});
// İngilizce mesajlar
$translateProvider.translations("en", {
'msg_header': 'The Associated Doctors',
'msg_langues': 'Diller',
'msg_agenda': "{{başlık}} {{ad}} {{soyadı}}'nın Günlüğü<br/> {{gün}} tarihinde",
'msg_calendrier': 'Takvim',
'msg_jour': 'Seçilen gün: ',
'msg_meteo': 'Bugün yağmur yağacak...'
});
// varsayılan dil
$translateProvider.preferredLanguage("fr");
}]);
- 2. satır: İlk değişiklik, yeni bir bağımlılığın eklenmesidir. Uygulamanın uluslararasılaştırılması için Angular modülü [pascalprecht.translate] gereklidir;
- 5-26. satırlar: [rdvmedecins] modülünün [config] işlevini tanımlar. Bir Angular uygulaması başlatıldığında, çerçeve, uygulama için gerekli tüm hizmetleri (Angular'ın önceden tanımlanmış olanları ve kullanıcı tarafından tanımlananlar) örneklendirir. Şu anda herhangi bir hizmet tanımlamadık. Uygulama modülündeki [config] işlevi, herhangi bir hizmet örneği oluşturulmadan önce yürütülür. Bu işlev, daha sonra örneklenecek hizmetlerin yapılandırma bilgilerini tanımlamak için kullanılabilir. Burada, [config] işlevi, uygulamanın uluslararasılaştırılmış mesajlarını tanımlamak için kullanılacaktır;
- 5. satır: [config] işlevinin parametresi, Oi’nin Angular tarafından sağlanan ve bilinen bir nesne olduğu ['O1', 'O2', ..., 'On', function(O1, O2, ..., On)] dizisidir. Burada, [$translateProvider] nesnesi, [pascalprecht.translate] modülü tarafından sağlanır. [function], uygulamayı yapılandırmak için çalıştırılan işlevdir;
- 7-14. satırlar: [$translateProvider.translations] işlevi iki parametre alır:
- birinci parametre bir dil anahtarıdır. Buraya istenilen herhangi bir değer gilebilir. Burada, Fransızca çeviriler için 'fr' (satır 7) ve İngilizce çeviriler için 'en' (satır 16) değerleri girilmiştir,
- ikincisi ise {'cle1':'msg1', 'cle2':'msg2', ...} biçiminde bir sözlük olarak verilen çeviri listesidir;
- 7-14. satırlar: Fransızca mesajlar;
- 16-23. satırlar: İngilizce mesajlar;
- 25. satır: [preferredLanguage] yöntemi varsayılan dili belirler. Bu yöntemin parametresi, [$translateProvider.translations] işlevinin ilk parametresi olarak kullanılan argümanlardan biridir; dolayısıyla burada ya 'fr' (7. satır) ya da 'en' (16. satır) olur;
- Üç tür mesaj olduğunu belirtelim:
- ne parametre ne de HTML öğeleri içeren mesajlar (satır 9, 11, 12, ...),
- HTML öğeleri içeren mesajlar (satır 8, 10, ...),
- parametre içeren mesajlar (satır 10, 19);
Şimdi [app-11.html]'i [app-12.html]'e kopyalayıp aşağıdaki değişiklikleri yapıyoruz:
<div class="container">
<!-- içinde HTML öğeleri bulunan ilk metin -->
<h3 class="alert alert-info" translate="{{'msg_header'}}"></h3>
<!-- parametreler içeren ikinci bir metin -->
<h3 class="alert alert-warning" translate="{{msg.text}}" translate-values="{{msg.model}}"></h3>
<!-- denetleyici tarafından çevrilmiş üçüncü bir metin -->
<h3 class="alert alert-danger">{{msg2}}</h3>
<pre>{{'msg_jour'|translate}}<em>{{jour | date:'fullDate' }}</em></pre>
<div class="row">
<!-- takvim-->
<div class="col-md-4">
<h4>{{'msg_calendrier'|translate}}</h4>
<div style="display:inline-block; min-height:290px;">
<datepicker ng-model="jour" show-weeks="true" class="well" min-date="minDate"></datepicker>
</div>
</div>
<!-- diller -->
<div class="col-md-2">
<div class="btn-group" dropdown is-open="isopen">
<button type="button" class="btn btn-primary dropdown-toggle" style="margin-top: 30px">
{{'msg_langues'|translate}}<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="" ng-click="setLang('fr')">Français</a></li>
<li><a href="" ng-click="setLang('en')">English</a></li>
</ul>
</div>
</div>
</div>
</div>
- çeviriler 3, 5, 9, 13, 23. satırlarda yapılmıştır;
- üç farklı sözdizimi ayırt edilebilir:
- [translate={{'msg_key'}}] sözdizimi (3. satır), burada [msg_key] bir çeviri sözlüğünün anahtarlarından biridir. Bu sözdizimi, HTML öğeleri olan veya olmayan mesajlar için uygundur, ancak parametre içeren mesajlar için uygun değildir;
- [translate={{'msg_key'}} translate-values={{dictionnaire]}}] sözdizimi (5. satır), HTML öğeleri olsun ya da olmasın ve parametre içeren mesajlar için uygundur;
- [{{'msg_key'|translate}}] sözdizimi (9., 13. ve 23. satırlar), parametre içermeyen ve HTML öğeleri bulunmayan mesajlar için uygundur;
Bu görünümdeki farklı mesajlara bir göz atalım:
Muayenehane<br/>Ortak Doktorlar | Ortak Doktorlar | |
Takvim | Takvim | |
Diller | Languages | |
Seçilen gün: | Seçilen gün: |
Şimdi 5. satırı inceleyelim:
<h3 class="alert alert-warning" translate="{{msg.text}}" translate-values="{{msg.model}}"></h3>
[msg.text] ve [msg.model]'in tırnak işaretleri içinde olmadığına dikkat edin. Bunlar karakter dizileri değil, şablon öğeleridir:
- msg.text: kullanılacak parametreli mesajın anahtarını tanımlar;
- msg.model: parametre değerlerini sağlayan sözlüktür;
[text, model] alanlarının adları herhangi bir şekilde olabilir. Görünümün [rdvMedecinsCtrl] denetleyicisinde, [msg] nesnesi şu şekilde tanımlanmıştır:

- 245. satır: [msg] nesnesinin tanımı;
- 245. satır: [text] alanının değeri, iki değerle ilişkili olan [msg_agenda] anahtarıdır:
- {{başlık}} {{ad}} {{soyad}}'ın ajandası<br/>{{gün}} tarihinde Fransızca sözlükte;
- {{unvan}} {{ad}} {{soyad}}'ın Günlüğü<br/> {{gün}} tarihinde İngilizce sözlükte;
Dolayısıyla, görüntülenecek mesajın dört parametresi vardır: [titre, prenom, nom, jour];
- 245. satır: [model] alanı, bu dört parametreye bir değer atayan bir sözlüktür. [jour] parametresiyle ilgili bir sorun vardır. Günün tam adını görüntülemek istiyoruz. Bu ad, Fransızca ve İngilizce dillerinde farklılık gösterir. Bu nedenle, görünümde {{ gün | date:'fullDate'}} biçiminde daha önce kullanılmış olan [date] filtresini kullanıyoruz. Javascript kodunda herhangi bir filtre, $filter('filter')(değer, ekler) biçiminde kullanılabilir; burada $filter, Angular'ın önceden tanımlanmış bir nesnesidir ve 'filter' ise filtrenin adıdır;
- 33-34. satırlar: önceden tanımlanmış $filter nesnesi denetleyiciye parametre olarak aktarılır; bu sayede 245. satırda kullanılabilir;
Görüntülenen görünümün başka bir satırına dönelim:
<!-- denetleyici tarafından çevrilmiş üçüncü bir metin -->
<h3 class="alert alert-danger">{{msg2}}</h3>
Önceki tüm çeviriler, [pascalprecht.translate] modülünün öznitelikleri kullanılarak görünümde gerçekleştirilmiştir. Bu çeviriyi sunucu tarafında da yapmaya karar verebiliriz. Burada da öyle yapılmıştır. Denetleyicide (yukarıdaki ekran görüntüsünde 247. satırda) şu kod bulunmaktadır:
$scope.msg2 = $filter('translate')('msg_meteo');
'translate' de bir filtre olduğu için 'date' filtresinde kullanılanla aynı sözdizimi kullanılır. Burada 'msg_meteo' anahtar mesajı talep edilmektedir.
Dil değiştirme mekanizmasını inceleyelim. [rdvmedecins] modülünün yapılandırma işlevi olan [config]'in Fransızca'yı varsayılan dil olarak belirlediğini görmüştük (aşağıdaki 9. satır):
// i18n yapılandırması
angular.module("rdvmedecins")
.config(['$translateProvider', function ($translateProvider) {
// Fransızca mesajlar
$translateProvider.translations("fr", {...});
// İngilizce mesajlar
$translateProvider.translations("en", {...});
// varsayılan dil
$translateProvider.preferredLanguage("fr");
}]);
Ayrıca, varsayılan yerel ayarın da Fransızca olduğunu hatırlatmak isteriz. [rdvmedecins] denetleyicisinin başlatılmasında şunlar yazılmıştır:
// yerel ayarı Fransızca olarak ayarlıyoruz
angular.copy(locales['fr'], $locale);
- 2. satır: [locales], bizim oluşturduğumuz bir sözlüktür;
[pascalprecht.translate] modülünün sağladığı mesajların uluslararasılaştırılması ile bizim uyguladığımız tarih yerelleştirme arasında hiçbir bağlantı yoktur. Tarih yerelleştirme, [pascalprecht.translate] modülü tarafından kullanılmayan $locale değişkenini kullanır. Bu iki işlem birbirinden bağımsızdır.
Şimdi, kullanıcı dil değiştirdiğinde neler olduğuna bir göz atalım:

- 251. satır: Dil değiştirildiğinde, [setLang] işlevi, ['fr','en'] parametrelerinden biriyle çağrılır;
- 252-257. satırlar: daha önce açıklanmıştı – takvimdeki [$locale] değişkenini değiştirirler. Bu, çevirilerin dilini etkilemez;
- 259. satır: Çevirilerin dili değiştirilir. [pascalprecht.translate] modülü tarafından sağlanan [$translate] nesnesi kullanılır. Bunun için nesnenin denetleyiciye eklenmesi gerekir:
// denetleyici
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope', '$locale', '$translate', '$filter',
function ($scope, $locale, $translate, $filter) {
Yukarıdaki 3. ve 4. satırlarda, $translate nesnesi eklenir;
- [$translate.use(lang)] işlevinin lang parametresinin değeri, yapılandırmada [$translateProvider.translations] işlevinin ilk parametresi olarak kullanılan anahtarlardan biri, yani ya 'fr' ya da 'en' olmalıdır. Durum tam da böyledir;
- 261. satır: msg2'nin değeri yeniden hesaplanıyor. Neden? Görünümde, 259. satırda gerçekleştirilen dil değişikliğinden sonra, mevcut tüm [translate] öznitelikleri yeniden değerlendirilecektir. Bu özniteliğe sahip olmayan {{msg2}} ifadesi için durum böyle olmayacaktır. Dolayısıyla, denetleyicide yeni değeri hesaplanır. [msg2]'in hesaplanmasında yeni dilin kullanılması için bu işlem, 259. satırdaki dil değişikliğinden sonra yapılmalıdır;
Eğer burada durursak, iki anomali gözlemlenir:
![]() |
- [1]'te, görünümün geri kalanı İngilizce olmasına rağmen gün Fransızca olarak kalmıştır;
- [2] ve [3]'te seçilen gün 24 Haziran iken, [1]'te gün 20 Haziran olarak sabit kalmıştır;
Çözüm bulmadan önce bazı açıklamalar yapalım. [1] mesajı, denetleyicide aşağıdaki kodla oluşturulmuştur:
$scope.msg = {'text': 'msg_agenda', 'model': {'titre': 'Mme', 'prenom': 'Laure', 'nom': 'PELISSIER', 'jour': $filter('date')($scope.jour, 'fullDate')}};
ve görünümde şu kodla görüntülenir:
<h3 class="alert alert-warning" translate="{{msg.text}}" translate-values="{{msg.model}}"></h3>
[1] hatası (günün adı Fransızca kalırken görünümün geri kalanı İngilizce), dil değişikliği sırasında [translate] özniteliği yeniden değerlendirildiğinde [translate-values] özniteliği için aynı işlem yapılmamış. Bu durumda, denetleyicide bu yeniden değerlendirmeyi zorlayabiliriz:
// ------------------- olay yöneticisi
// dil değişikliği
$scope.setLang = function (lang) {
...
// msg2 güncelleniyor
$scope.msg2 = $filter('translate')('msg_meteo');
// ve mesajın gününü
$scope.msg.model.jour = $filter('date')($scope.jour, 'fullDate');
};
Her dil değişikliğinde, yukarıdaki 8. satır görüntülenen günü yeniden değerlendirir. Bu, ilk sorunu etkili bir şekilde çözer ancak ikinci sorunu çözmez (takvimde başka bir gün seçildiğinde mesajda görüntülenen gün değişmez). Bu davranışın nedeni şudur: Mesaj, aşağıdaki kodla görünümde görüntülenir:
<h3 class="alert alert-warning" translate="{{msg.text}}" translate-values="{{msg.model}}"></h3>
Görüntülenen V görünümü, yalnızca şablonu M değiştiğinde değişir. Ancak burada, takvimde yeni bir gün seçilmesi, yönetilmeyen bir olayı tetikler; bu da [msg] şablonunun değişmemesine ve dolayısıyla görünümün de değişmemesine neden olur. Görünümde takvim tanımını şu şekilde değiştiriyoruz:
<datepicker ng-model="jour" show-weeks="true" class="well" min-date="minDate"
ng-click="calendarClick()"></datepicker>
Yukarıda, takvime yapılan tıklamanın [$scope.calendarClick] işlevi tarafından işlenmesi gerektiğini belirtiyoruz. Bu işlev şu şekildedir:

- 267. satır: takvime tıklama işleyicisi;
- 269. satır: [msg] mesajıyla görüntülenen günün güncellenmesi zorlanıyor;
3.7.4. Örnek 4: Bir yapılandırma hizmeti
JS adlı bir Angular uygulamasının mimarisine geri dönelim:
![]() |
Burada hizmet kavramına odaklanacağız. Bu oldukça geniş bir kavramdır. Yukarıda [DAO] katmanı açıkça bir hizmet olsa da, her Angular nesnesi bir hizmet haline gelebilir:
- bir hizmet belirli bir sözdizimini izler. Bir adı vardır ve Angular bu ad aracılığıyla onu tanır;
- bir hizmet, Angular tarafından denetleyicilere ve diğer hizmetlere enjekte edilebilir;
[rdvmedecins] modülünde yapılandıracağımız hizmetlerin bazılarının yapılandırılması gerekecektir. Bir hizmet başka bir hizmete enjekte edilebildiğinden, [config] adını vereceğimiz bir hizmet içinde yapılandırmayı yapmak ve bunu yapılandırılacak hizmetlere ve denetleyicilere enjekte etmek cazip gelebilir. Şimdi bu süreci açıklayacağız.
[app-13.html] modülünü [app-14.html] olarak kopyalayıp aşağıdaki değişiklikleri yapıyoruz:
<div class="container">
<!-- bekleyen mesaj kontrolü -->
<label>
<input type="checkbox" ng-model="waiting.visible">
<span>Voir le message d'attente</span>
</label>
<!-- bekleyen mesaj -->
<div class="alert alert-warning" ng-show="waiting.visible">
<h1>{{ waiting.text | translate}}
<button class="btn btn-primary pull-right" ng-click="waiting.cancel()">
{{'msg_cancel'|translate}}</button>
<img src="assets/images/waiting.gif" alt=""/>
</h1>
</div>
...
</div>
...
<script type="text/javascript" src="rdvmedecins-02.js"></script>
- 3-6. satırlar: 9-15. satırlardaki bekleme mesajının görüntülenip görüntülenmeyeceğini kontrol eden bir onay kutusu. Onay kutusunun değeri, V görünümünün M modelindeki [waiting.visible] değişkenine atanır. Bu değer, onay kutusu işaretliyse true, aksi takdirde false olur. Bu, her iki yönde de çalışır. [waiting.visible] değişkenine true değeri atarsak, onay kutusu işaretlenir. V görünümü ile M modeli arasında çift yönlü bir ilişki vardır;
- 9-15. satırlar: bir bekleme mesajı ve beklemeyi iptal etme düğmesi (11. satır);
- 9. satır: mesaj, yalnızca [waiting.visible] değişkeninin değeri true ise görünür. Dolayısıyla, 4. satırdaki kutucuğu işaretlediğimizde:
- true değeri, [waiting.visible] değişkenine atanır (ng-model, satır 4);
- M modelinde bir değişiklik olduğu için V görünümü otomatik olarak yeniden değerlendirilir. Bekleme mesajı bu durumda görünür hale gelir (ng-show, 9. satır);
- 4. satırdaki onay kutusunun işaretini kaldırdığımızda da mantık aynıdır: bekleme mesajı gizlenir;
- 10. satır: Bekleme mesajı çeviriye tabi tutulur (translate filtresi);
- 11. satır: Düğmeye tıklandığında, [waiting.cancel()] yöntemi yürütülür (ng-click özniteliği);
- 12. satır: Düğmenin etiketi çevrilir;
- 19. satır: Uygulamanın JavaScript kodu, halihazırda yazılmış ve şimdi yeniden düzenlenmesi gereken kodun kaybolmaması için yeni bir dosyaya (JS [rdvmedecins-02]) aktarılır;
Bu, aşağıdaki görünümü verir:
![]() |
- [1]'te, kutucuk işaretlenmemiş;
- [2]'e, kutucuk işaretli;
[rdvmedecins-02] komut dosyası, [rdvmedecins] komut dosyasının yeniden düzenlenmiş halidir:

- 6. satır: uygulamanın [rdvmedecins] modülü;
- 9-10. satırlar: uygulamanın yapılandırma işlevi;
- 38-39. satırlar: [config] hizmeti;
- 283-284. satırlar: [rdvMedecinsCtrl] denetleyicisi;
Daha önce, denetleyicide 200 satırlık locales={'fr':..., 'en': ...} sözlüğünü tanımlamıştık. Bu sözlük açıkça bir yapılandırma öğesidir, bu nedenle 38-39. satırlardaki [config] hizmetine taşınır. Bu hizmet şu şekilde tanımlanmıştır:

- 38-39. satırlar: [angular.module] nesnesinin [factory] işlevi kullanılarak bir hizmet oluşturulur. Bu fonksiyonun sözdizimi öncekilerle aynıdır: factory('nom_service',['O1','O2', ...., 'On', function (O1, O2, ..., On){...}]); burada Oi'ler, Angular tarafından tanınan (önceden tanımlanmış veya geliştirici tarafından oluşturulmuş) nesne adlarıdır ve Angular bunları factory fonksiyonunun parametresi olarak enjekte eder. Burada fonksiyonun parametresi olmadığı için, aynı şekilde kabul edilen daha kısa bir sözdizimi kullanılmıştır: factory('nom_service', function (){...}]);
- 40. satır: [factory] işlevi, döndürdüğü bir nesne aracılığıyla hizmeti uygulamalıdır. Hizmet, işte bu nesnedir. Bu nedenle işlev, factory (nesne oluşturma fabrikası) olarak adlandırılır;
Genel olarak bir hizmetin kodu şu şekildedir:
Angular.module('nom_module')
.factory('nom_service',['O1','O2', ...., 'On', function (O1, O2, ..., On){
// hizmetin hazırlanması
...
// hizmeti uygulayan nesne oluşturulur
return {
// alanlar
...
// yöntemler
...
}
});
- 6. satır: Hem alanlar hem de yöntemler içerebilen bir JS nesnesi döndürülür. Hizmeti sağlayan bu yöntemlerdir;
Burada [config] hizmeti yalnızca alanlar tanımlar, yöntem tanımlamaz. Uygulamada ayarlanabilecek her şey buraya eklenecektir:
- 42-47. satırlar: çevrilecek mesajların anahtarları;
- 59-62. satırlar: uygulamanın URL'leri;
- 64-69. satırlar: uzak web hizmetinin URL'leri;
- 71. satır: Yanıt vermeyen bir web hizmetine yapılan HTTP çağrısı, uzun sürebilir. Burada, web hizmetinin yanıtı için maksimum bekleme süresi 1 saniye olarak belirlenir. Bu süre geçtikten sonra, HTTP çağrısı başarısız olur ve bir JS istisnası tetiklenir;
- 73. satır: Sunucuya her çağrıdan önce, burada milisaniye cinsinden belirlenen süre boyunca bir bekleme simülasyonu yapılacaktır. Bekleme süresi 0 olarak ayarlandığında bekleme olmaz. Uygulama, kullanıcının başlattığı bir işlemi iptal edebileceği şekilde tasarlanacaktır. İşlemin iptal edilebilmesi için en az birkaç saniye sürmesi gerekir. Uzun süren işlemleri simüle etmek için bu yapay bekleme süresi kullanılacaktır;
- 75. satır: [debug=true] modunda, geçerli görünümde ek bilgiler görüntülenir. Varsayılan olarak bu mod etkindir. Üretim ortamında bu alan false olarak ayarlanır;
- 77-278. satırlar: 'fr' ve 'en' yerel ayarlarının sözlüğü. Bu sözlük daha önce [rdvMedecinsCtrl] denetleyicisinde yer alıyordu;
Bu hizmetle birlikte, [rdvMedecinsCtrl] denetleyicisi şu şekilde değişir:

- 284-285. satırlar: [config] hizmeti denetleyiciye eklenir;
- 290. satır: [locales] sözlüğü artık denetleyicide değil, [config] hizmetinde bulunur;
- 294. satır: Bekleme mesajının görüntülenmesini kontrol eden [waiting] nesnesi. Bekleme mesajının anahtarı, [config] hizmetinde bulunur (text alanı). Varsayılan olarak bekleme mesajı gizlidir (visible alanı). cancel alanının değeri, 316. satırdaki fonksiyonun adıdır. Dolayısıyla bu alan bir yöntem veya fonksiyondur;
- 316. satır: [cancel] işlevi özeldir ($scope.cancel=function(){} yazılmamıştır). İptal düğmesinin koduna geri dönelim:
<button class="btn btn-primary pull-right" ng-click="waiting.cancel()">
Kullanıcı iptal düğmesine tıkladığında, [$scope.waiting.cancel()] yöntemi çağrılır. Sonuçta, 316. satırdaki özel cancel işlevi yürütülür. Bu işlev, [waiting.visible] model değişkenini (318. satır) false olarak ayarlayarak bekleme mesajını gizler;
3.7.5. Örnek 5: Asenkron programlama
Şimdi, asenkron programlama kavramını içeren yeni bir hizmet sunacağız.
![]() |
Uygulamamızda üç hizmet bulunacaktır:
- [config]: az önce tanıttığımız yapılandırma hizmeti;
- [utils]: yardımcı yöntemler hizmeti. Bunlardan ikisini tanıtacağız;
- [dao]: randevu alma web hizmetine erişim hizmeti. Bunu yakında tanıtacağız;
Aşağıdaki uygulamayı yazacağız:
![]() |
![]() |
- Buradaki amaç, [2] başlığını [1] tarafından belirlenen süre boyunca görüntülemektir. Bekleme, [3] ile iptal edilebilir.
[app-01.html] kodunu [app-15.html]'e kopyalayıp kodu şu şekilde değiştiriyoruz:
<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
<title>RdvMedecins</title>
...
</head>
<body ng-controller="rdvMedecinsCtrl">
<div class="container">
<!-- bekleme mesajı -->
<div class="alert alert-warning" ng-show="waiting.visible" ng-cloak="">
<h1>{{ waiting.text | translate}}
<button class="btn btn-primary pull-right" ng-click="waiting.cancel()">{{'msg_cancel'|translate}}</button>
<img src="assets/images/waiting.gif" alt=""/>
</h1>
</div>
<!-- form -->
<div class="alert alert-info" ng-hide="waiting.visible">
<div class="form-group">
<label for="waitingTime">{{waitingTimeText | translate}}</label>
<input type="text" id="waitingTime" ng-model="waiting.time"/>
</div>
<button class="btn btn-primary" ng-click="execute()">Exécuter</button>
</div>
</div>
..
<script type="text/javascript" src="rdvmedecins-03.js"></script>
</body>
</html>
- 11. satır: [ng-cloak] özniteliği, alanın Angular ifadeleri hesaplanana kadar alanın görüntülenmesini engeller. Bu, [ng-show] özniteliğinin değerlendirilmesinden önce alanın kısa süreliğine görüntülenmesini önler; bu özniteliğin değerlendirilmesi aslında alanın gizlenmesine neden olur;
- 22. satır: Kullanıcının girişi (bekleme süresi) [waiting.time] modelinde (ng-model özniteliği) saklanacaktır;
- 28. satır: Sayfa, yeni bir [rdvmedecins-03] komut dosyası kullanır;
[rdvmedecins-03] komut dosyası şöyledir:

- 6. satır: Uygulamayı yöneten Angular modülü;
- 10. satır: Mesajları uluslararası hale getirmek için kullanılan [config] işlevi;
- 41. satır: daha önce anlattığımız [config] hizmeti;
- 286. satır: Oluşturacağımız [utils] hizmeti;
- satır 315: oluşturacağımız [rdvmedecinsCtrl] denetleyicisi;
[config] işlevine yeni bir mesaj anahtarı ekliyoruz (6. ve 11. satırlar):
angular.module("rdvmedecins")
.config(['$translateProvider', function ($translateProvider) {
// Fransızca mesajlar
$translateProvider.translations("fr", {
...
'msg_waiting_time_text': "Bekleme süresi: "
});
// İngilizce mesajlar
$translateProvider.translations("en", {
...
'msg_waiting_time_text': "Bekleme süresi:"
});
// varsayılan dil
$translateProvider.preferredLanguage("fr");
}]);
[config] hizmetine bu mesaj anahtarı için yeni bir satır (satır 6) ekliyoruz:
angular.module("rdvmedecins")
.factory('config', function () {
return {
// uluslararasılaştırılacak mesajlar
...
waitingTimeText: 'msg_waiting_time_text',
[utils] hizmeti iki yöntem içerir (4. ve 12. satırlar):
angular.module("rdvmedecins")
.factory('utils', ['config', '$timeout', '$q', function (config, $timeout, $q) {
// bir nesnenin JSON temsilinin görüntülenmesi
function debug(message, data) {
if (config.debug) {
var text = data ? message + " : " + angular.toJson(data) : message;
console.log(text);
}
}
// bekleme
function waitForSomeTime(milliseconds) {
// asenkron bekleme süresi (milisaniye)
var task = $q.defer();
$timeout(function () {
task.resolve();
}, milliseconds);
// görev geri döndürülür
return task;
};
// hizmet örneği
return {
debug: debug,
waitForSomeTime: waitForSomeTime
}
}]);
- 2. satır: hizmetin adı [utils]'tir (1. parametre). Bu hizmetin üç hizmete bağımlılığı vardır: önceden tanımlanmış iki Angular hizmeti olan $timeout ve $q ile config hizmeti. [$timeout] hizmeti, belirli bir süre geçtikten sonra bir işlevin çalıştırılmasını sağlar. [$q] hizmeti, asenkron görevler oluşturulmasına olanak tanır;
- 4. satır: [debug] yerel işlevi;
- 12. satır: [waitForSomeTime] yerel işlevi;
- satır 23-26: [utils] hizmetinin örneği. Bu, satır 4 ve 12'deki yöntemleri sunan bir nesnedir. Nesnenin alanlarına herhangi bir isim verilebileceğini unutmayın. Tutarlılık amacıyla, bu alanlara referans verdikleri işlevlerin isimleri verilmiştir;
- satır 4-9: [debug] yöntemi, konsola bir [message] mesajı ve gerekirse bir [data] nesnesinin JSON temsilini yazar. Bu sayede, herhangi bir karmaşıklıkta nesneler görüntülenebilir;
- 12-20. satırlar: [waitForSomeTime] yöntemi, [milliseconds] milisaniye süren bir asenkron görev oluşturur;
- 14. satır: önceden tanımlanmış [$q] nesnesi (https://docs.angularjs.org/api/ng/service/$q) kullanılarak bir görev oluşturulur. Aşağıda, Angular belgelerinde [deferred] olarak adlandırılan görevin API nesnesi yer almaktadır:

- [task] adlı asenkron görev, [$q.defer()] komutuyla oluşturulur;
- bu görev, aşağıdaki iki yöntemden biri kullanılarak sonlandırılır:
- [task.resolve(value)]: görevi başarıyla sonlandırır ve görevin bitmesini bekleyenlere [value] değerini döndürür;
- [task.reject(value)]: görevi başarısızlıkla sonlandırır ve görevin bitmesini bekleyenlere [value] değerini döndürür;
[task] görevi, görevinin tamamlanmasını bekleyenlere düzenli olarak bilgi verebilir:
- [task.notify(value)]: görevin tamamlanmasını bekleyenlere [value] değerini gönderir. Görev çalışmaya devam eder;
Görevin tamamlanmasını beklemek isteyenler, görevin [promise] alanını kullanır:
[promise] nesnesinin aşağıdaki API değeri vardır (http://www.frangular.com/2012/12/api-promise-angularjs.html):

Görevin hem başarılı hem de başarısız durumlarını yönetmek için şu kod yazılır:
- 1. satır: görevin sözünü alırız;
- 2. satır: Başarı veya başarısızlık durumunda yürütülecek işlevleri tanımlarız. Başarısızlık işlevi tanımlanmayabilir. [successCallback] işlevi, yalnızca [task] görevinin [task.resolve()] başarı koduyla tamamlanmasının ardından çalıştırılacaktır. [errorCallBack] işlevi, yalnızca [task] görevinin [task.reject()] hatasıyla sonuçlanması durumunda yürütülecektir.
- 3. satır: Önceki iki işlevden birinin yürütülmesinden sonra yürütülecek işlev tanımlanır. Buraya, her iki işlev için ortak olan [successCallback, errorCallBack] kodunu yazıyoruz.
[waitForSomeTime] işlevinin koduna geri dönelim:
// bekleme
function waitForSomeTime(milliseconds) {
// milisaniye cinsinden asenkron bekleme
var task = $q.defer();
$timeout(function () {
task.resolve();
}, milliseconds);
// görev geri döndürülüyor
return task;
};
- 4. satır: bir görev oluşturulur;
- 5-7. satırlar: [$timeout] nesnesi, milisaniye cinsinden belirtilen belirli bir süre (2. parametre) geçtikten sonra çalıştırılacak bir fonksiyonun (1. parametre) tanımlanmasına olanak tanır. Burada [$timeout] işlevinin ikinci parametresi, yöntemin parametresidir (1. satır);
- 6. satır: [milliseconds] süresinin sonunda görev başarıyla tamamlanır;
- satır 9: [task] görevi döndürülür. Burada, 9. satırın [$timeout] nesnesinin tanımlanmasından hemen sonra yürütüldüğü anlaşılmalıdır. [milliseconds] zaman aşımının dolmasını beklemiyoruz. Dolayısıyla, 2-10. satırlardaki kod iki farklı zamanda yürütülür:
- ilk olarak [$timeout] nesnesini tanımlarken;
- ikinci kez ise [milliseconds] zamanlayıcısı sona erdiğinde;
Burada asenkron bir işlev söz konusudur: sonucu, yürütülme anından sonra elde edilir.
[config] hizmetini kullanan denetleyicinin kodu şöyledir:
// denetleyici
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', '$filter',
function ($scope, utils, config, $filter) {
// ------------------- model başlatma
// bekleme mesajı
$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time: undefined};
$scope.waitingTimeText = config.waitingTimeText;
// bekleme görevi
var task;
// günlükler
utils.debug("libellé temps d'attente", $filter('translate')($scope.waitingTimeText));
utils.debug("locales['fr']=", config.locales['fr']);
// eylem yürütme
$scope.execute = function () {
// günlük
utils.debug('début', new Date());
// bekleme mesajı görüntüleniyor
$scope.waiting.visible = true;
// simüle edilmiş bekleme
task = utils.waitForSomeTime($scope.waiting.time);
// bekleme sonu
task.promise.then(function () {
// başarılı
utils.debug('fin', new Date());
}, function () {
// başarısızlık
utils.debug('Opération annulée')
});
task.promise['finally'](function () {
// her durumda bekleme sonu
$scope.waiting.visible = false;
});
};
// bekleme iptali
function cancel() {
// görev tamamlanıyor
task.reject();
}
}]);
- 3. satır: Denetleyici, [config] hizmetini kullanır;
- 7. satır: [time] alanı, [$scope.waiting] nesnesine eklenmiştir. [$scope.waiting.time] nesnesi, kullanıcı tarafından belirlenen bekleme süresi değerini alır;
- 8. satır: Görünüm tarafından görüntülenen bekleme mesajının anahtarı, [$scope.waitingTimeText] şablonuna yerleştirilir. Genel olarak, bir V görünümü tarafından görüntülenen her şey [$scope] nesnesine yerleştirilmelidir;
- 10. satır: Yerel bir değişken. Bu değişken V görünümüne açık değildir;
- satır 12-13: [config] hizmetinin [debug] yöntemi kullanılır. Konsolda şu sonuç elde edilir:
- satırda, locales['fr'] nesnesinin JSON notasyonu elde edilir.
- 16. satır: Kullanıcı [Executer] düğmesine tıkladığında yürütülen yöntem;
- 18. satır: Yöntemin yürütülmeye başlama saatini gösterir;
- 22. satır: [waitForSomeTime] görevi başlatılır. Görevin bitmesi beklenmez. Yürütme, sonraki 24. satırla devam eder;
- satır 24-30: görev başarıyla tamamlandığında (satır 26) ve hata durumunda (satır 29) yürütülecek işlevler tanımlanır;
- 26. satır: Yöntemin yürütülmesinin bittiği saati gösterir;
- 29. satır: işlemin iptal edildiğini gösterir. Bu durum, yalnızca kullanıcı [Annuler] düğmesine tıkladığında gerçekleşir. 41. satırdaki komut, asenkron görevi bir hata koduyla durdurur;
- satır 31-34: önceki iki işlevden birinin yürütülmesinden sonra yürütülecek işlev tanımlanır;
Bu kodun yürütme sırasını anlamak önemlidir. Kullanıcının 3 saniyelik bir gecikme süresi ayarladığı ve beklemeyi iptal etmediği durumda:
- [Exécuter] düğmesine tıkladığında, [$scope.execute] işlevi yürütülür. 16-34. satırlar, 3 saniyelik bekleme süresi dolmadan yürütülür. Bu yürütme işlemi tamamlandığında, V görünümü M modeli ile senkronize edilir. Bekleme mesajı görüntülenir (ng-show=$scope.waiting.visible=true, satır 20) ve form gizlenir (ng-hide=$scope.waiting.visible=true, satır 20);
- bu andan itibaren kullanıcı, görünümle yeniden etkileşime girebilir. Özellikle [Annuler] düğmesine tıklayabilir;
- eğer bunu yapmazsa, 3 saniye sonra [$timeout] işlevi (aşağıdaki 5-7. satırlara bakınız) çalıştırılır:
// bekleme
function waitForSomeTime(milliseconds) {
// milisaniye milisaniye asenkron bekleme
var task = $q.defer();
$timeout(function () {
task.resolve();
}, milliseconds);
// görev geri döndürülüyor
return task;
};
- yani 3 saniye sonra kod çalıştırılır. Bu kod, [task] görevini bir başarı kodu (resolve) ile sonlandırır. Bu, bu sonlanmayı bekleyen tüm kodların çalıştırılmasını tetikleyecektir (aşağıdaki 4. satır):
// simüle edilmiş bekleme
task = utils.waitForSomeTime($scope.waiting.time);
// bekleme sonu
task.promise.then(function () {
// başarılı
utils.debug('fin', new Date());
}, function () {
// başarısızlık
utils.debug('Opération annulée')
});
task.promise['finally'](function () {
// her durumda bekleme sonu
$scope.waiting.visible = false;
});
- Yukarıdaki 6. satır (başarılı sonlandırma) bu nedenle çalıştırılacaktır. Ardından 11-14. satırların sırası gelecektir. Bu kod çalıştırıldıktan sonra, V görünümüne geri dönülür ve bu görünüm M modeliyle senkronize edilir. Bekleme mesajı gizlenir (ng-show=$scope.waiting.visible=false, 13. satır) ve form görüntülenir (ng-hide=$scope.waiting.visible=false, 13. satır);
Ekran görüntüleri şu şekildedir:
Yukarıda, bekleme süresinin başlangıcı ile sonu arasında 3 saniyelik bir süre (06:01-05:58) olduğu görülmektedir. Buna karşın, kullanıcı 3 saniye dolmadan beklemeyi iptal ederse, aşağıdaki ekran görüntülenir:
Son olarak, herhangi bir anda yalnızca UI (Kullanıcı Arayüzü) olarak adlandırılan tek bir yürütme iş parçacığı olduğunu anlamak önemlidir. Asenkron bir görevin sonu, tıpkı bir düğmeye tıklanması gibi bir olayla bildirilir. Bu olay hemen işlenmez. Yürütülmeyi bekleyen olay kuyruğuna eklenir. Sırası geldiğinde işlenir. Bu işlem, UI iş parçacığını kullanır ve bu nedenle bu süre boyunca arayüz donar. Kullanıcı girişlerine tepki vermez. Bu nedenle, bir olayın hızlı bir şekilde işlenmesi önemlidir. Her olay UI iş parçacığı tarafından işlendiğinden, aynı anda çalışan iş parçacıkları arasındaki senkronizasyon sorunlarını çözmek zorunda kalınmaz. Her an yalnızca UI iş parçacığı çalışır.
3.7.6. Örnek 6: HTTP hizmetleri
Şimdi, web sunucusuyla iletişim kuran [dao] hizmetini tanıtıyoruz:
![]() |
3.7.6.1. V görünümü
![]() |
Doktorların listesini istemek için bir form yazacağız:

[app-01.html]'i [app-16.html]'e kopyalayıp, ardından şu şekilde değiştiriyoruz:
<div class="container" ng-cloak="">
<h1>Rdvmedecins - v1</h1>
<!-- bekleme mesajı -->
<div class="alert alert-warning" ng-show="waiting.visible" ng-cloak="">
<h1>{{ waiting.text | translate}}
<button class="btn btn-primary pull-right" ng-click="waiting.cancel()">{{'msg_cancel'|translate}}</button>
<img src="assets/images/waiting.gif" alt=""/>
</h1>
</div>
<!-- istek -->
<div class="alert alert-info" ng-hide="waiting.visible">
<div class="form-group">
<label for="waitingTime">{{waitingTimeText | translate}}</label>
<input type="text" id="waitingTime" ng-model="waiting.time"/>
</div>
<div class="form-group">
<label for="urlServer">{{urlServerLabel | translate}}</label>
<input type="text" id="urlServer" ng-model="server.url"/>
</div>
<div class="form-group">
<label for="login">{{loginLabel | translate}}</label>
<input type="text" id="login" ng-model="server.login"/>
</div>
<div class="form-group">
<label for="password">{{passwordLabel | translate}}</label>
<input type="password" id="password" ng-model="server.password"/>
</div>
<button class="btn btn-primary" ng-click="execute()">{{medecins.title|translate:medecins.model}}</button>
</div>
<!-- doktor listesi -->
<div class="alert alert-success" ng-show="medecins.show">
{{medecins.title|translate:medecins.model}}
<ul>
<li ng-repeat="medecin in medecins.data">{{medecin.titre}}{{medecin.prenom}} {{medecin.nom}}</li>
</ul>
</div>
<!-- hata listesi -->
<div class="alert alert-danger" ng-show="errors.show">
{{errors.title|translate:errors.model}}
<ul>
<li ng-repeat="message in errors.messages">{{message|translate}}</li>
</ul>
</div>
</div>
...
<script type="text/javascript" src="rdvmedecins-04.js"></script>
- 13-31. satırlar: Formu hayata geçirir. Bekleme mesajı görüntülendiğinde bu form görünmez (ng-hide="waiting.visible"). Dört girişin (ng-model öznitelikleri) [waiting.time (ligne 16), server.url (ligne 20), server.login (ligne 24), server.password (ligne 28)] içinde saklandığını unutmayalım;
- 34-39. satırlar: doktor listesini görüntüler. Bu liste her zaman görünür değildir (ng-show="medecins.show").
- 35. satır: Daha önce karşılaştığımız <div ... translate="{{medecins.title}}" translate-values="{{medecins.model}}"> sözdizimine bir alternatif;
- 36. satır: sıralanmamış bir liste;
- 37. satır: Doktor listesi [medecins.data] şablonunda bulunur. Angular'ın [ng-repeat] yönergesi, bir listeyi taramaya olanak tanır. ng-repeat="medecin in medecins.data" sözdizimi, <li> etiketinin [medecins.data] listesindeki her öğe için tekrarlanmasını ister. Listenin geçerli öğesi [medecin] olarak adlandırılır;
- 37. satır: Her <li> etiketi için, [medecin] değişkeniyle belirtilen mevcut doktorun unvanı, adı ve soyadı yazılır;
- 42-47. satırlar: Hata listesini gösterir. Bu liste her zaman görünür değildir (ng-show="errors.show"). Bu görüntüleme, doktor listesinin görüntülenmesiyle aynı şablonu izler. Genel olarak, bir nesne listesini görüntülemek için Angular [ng-repeat] yönergesi kullanılır;
- 51. satır: JavaScript kodu artık [rdvmedecins-04] dosyasında yer almaktadır
3.7.6.2. C denetleyicisi ve M modeli
![]() |
Javascript kodu şu şekilde değişir:

- 6-9. satırlar: [rdvmedecins] modülü, projenin bağımlılıklarından biri olan [angular-base64] kütüphanesi tarafından sağlanan [base64] modülüne bağımlılık bildirir. Bu modül, kimlik doğrulama amacıyla web hizmetine gönderilen [login:password] dizesini Base64 ile kodlamak için kullanılır;
- 12-13. satırlar: Uluslararasılaştırılmış mesajlarımızı içeren başlatma işlevi. Yeni mesajlar ortaya çıkıyor. Bunları artık tek tek göstermeyeceğiz;
- 69-70. satırlar: Uygulamamızı yapılandıran [config] hizmeti. Buraya yeni mesaj anahtarları eklenmiştir. Bunları artık tek tek tanıtmayacağız;
- satır 318-319: yardımcı yöntemleri içeren [utils] hizmeti. Bunlara yenileri eklenmiştir. Bunları tanıtacağız;
- satır 385-386: web hizmeti ile iletişimi sağlayan [dao] hizmeti. Odaklanacağımız nokta budur;
- 467-468. satırlar: Az önce tanıttığımız V görünümünün C denetleyicisi. Kullanıcı isteklerine yanıt veren orkestra şefi görevi gördüğü için şimdi onu tanıtacağız;
3.7.6.3. C denetleyicisi
Denetleyicinin kodu şöyledir:
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao', '$translate',
function ($scope, utils, config, dao, $translate) {
// ------------------- şablon başlatma
// şablon
$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time: undefined};
$scope.waitingTimeText = config.waitingTimeText;
$scope.server = {url: undefined, login: undefined, password: undefined};
$scope.medecins = {title: config.listMedecins, show: false, model: {}};
$scope.errors = {show: false, model: {}};
$scope.urlServerLabel = config.urlServerLabel;
$scope.loginLabel = config.loginLabel;
$scope.passwordLabel = config.passwordLabel;
// asenkron görev
var task;
// eylemin yürütülmesi
$scope.execute = function () {
// UI güncelleniyor
$scope.waiting.visible = true;
$scope.medecins.show = false;
$scope.errors.show = false;
// simüle edilmiş bekleme
task = utils.waitForSomeTime($scope.waiting.time);
var promise = task.promise;
// bekleme
promise = promise.then(function () {
// doktor listesi isteniyor;
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password, config.urlSvrMedecins);
return task.promise;
});
// önceki çağrının sonucu analiz ediliyor
promise.then(function (result) {
// sonuç={hata: 0, veriler: [med1, med2, ...]}
// sonuç={hata: n, mesajlar: [msg1, msg2, ...]}
if (result.err == 0) {
// toplanan veriler şablona eklenir
$scope.medecins.data = result.data;
// UI güncellenir
$scope.medecins.show = true;
$scope.waiting.visible = false;
} else {
// doktor listesini alırken hatalar oluştu
$scope.errors = { title: config.getMedecinsErrors, messages: utils.getErrors(result), show: true, model: {}};
// UI dosyası güncelleniyor
$scope.waiting.visible = false;
}
});
};
// iptal bekleniyor
function cancel() {
// görev tamamlanıyor
task.reject();
// UI güncelleniyor
$scope.waiting.visible = false;
$scope.medecins.show = false;
$scope.errors.show = false;
}
}
])
;
- 2. satır: denetleyicinin [dao] hizmetine yönelik yeni bir bağımlılığı vardır;
- 6-13. satırlar: V görünümünün ilk görüntülenmesi için M modeli başlatılır;
- 8. satır: [$scope.server], V formundaki dört bilgiden üçünü almak için kullanılacak; dördüncü bilgi ise [$scope.waiting.time]'te (6. satır) saklanmaktadır;
- 9. satır: [$scope.medecins], doktor listesinin görüntülenmesi için gerekli bilgileri toplayacaktır:
<!-- doktor listesi -->
<div class="alert alert-success" ng-show="medecins.show">
{{medecins.title|translate:medecins.model}}
<ul>
<li ng-repeat="medecin in medecins.data">{{medecin.titre}}{{medecin.prenom}} {{medecin.nom}}</li>
</ul>
</div>
[medecins.title] özniteliği, başlık çubuğunun başlığı olacaktır. Bu öznitelik, [config] hizmetinde tanımlanmıştır. [medecins.show] özniteliği, başlığın görüntülenip görüntülenmeyeceğini kontrol edecektir (ng-show="medecins.show" özniteliği). [medecins.model] özniteliği boş bir sözlüktür ve öyle kalacaktır. Bu öznitelik, yalnızca 3. satırda kullanılan çeviri varyantının kullanımını göstermek amacıyla kullanılmaktadır. Henüz tanımlanmamış olan [medecins.data] özniteliği, doktorların listesini içerecektir (5. satır).
- 10. satır: [$scope.errors], hata listesinin görüntülenmesi için gerekli bilgileri toplayacaktır:
<!-- hata listesi -->
<div class="alert alert-danger" ng-show="errors.show">
{{errors.title|translate:errors.model}}
<ul>
<li ng-repeat="message in errors.messages">{{message|translate}}</li>
</ul>
</div>
[errors.title] özniteliği, başlığın başlığı olacaktır. Bu öznitelik, [config] hizmetinde tanımlanmıştır. [errors.show] özniteliği, başlığın görüntülenip görüntülenmeyeceğini kontrol edecektir (ng-show="errors.show" özniteliği). [errors.model] özniteliği boş bir sözlüktür ve öyle kalacaktır. Bu öznitelik, yalnızca 3. satırda kullanılan çeviri varyantının kullanımını göstermek amacıyla kullanılmaktadır. Henüz tanımlanmamış olan [errors.messages] özniteliği, görüntülenecek hata mesajlarının listesini içerecektir (5. satır).
- 16. satır: asenkron görev. Denetleyici, arka arkaya iki asenkron görevi başlatacaktır. Bu ardışık görevlere ilişkin referanslar, [task] değişkenine yerleştirilecektir. Bu, görevlerin iptal edilmesini sağlayacaktır (55. satır);
- 19. satır: Kullanıcı [Liste des médecins] düğmesine tıkladığında yürütülen yöntem:
<button class="btn btn-primary" ng-click="execute()">Liste des médecins</button>
- satır 21-23: görsel arayüz güncellenir: bekleme mesajı görüntülenir, geri kalan her şey gizlenir;
- 25. satır: Bekleme için asenkron görev oluşturulur. Kullanıcı tarafından formda girilen süre dolduğunda bir sinyal (görev tamamlandı) alınacaktır;
- 26. satır: Asenkron görevin sözü alınır. Görevi başlatan program bu sözle çalışır. Ancak görevi iptal edebilmek için (55. satır) görevin kendisine ait referansa sahip olmak gerekir;
- 28-32. satırlar: Bekleme süresi sona erdiğinde yapılacak iş tanımlanır;
- satır 30: [dao.getData] yöntemi kullanılarak yeni bir asenkron görev başlatılır. Göreve ihtiyaç duyduğu bilgiler aktarılır:
- [$scope.server.url] web hizmetinin kök URL'i, örneğin [http://localhost:8080];
- kimlik doğrulama için [$scope.server.login], örneğin [admin];
- oturum açmak için [$scope.server.password] şifresi, örneğin [admin];
- URL, istenen hizmeti [config.urlSvrMedecins]'e sağlar; burada [/getAllMedecins]. Sonuç olarak, tam URL, [http://localhost:8080/getAllMedecins] olacaktır;
[dao.getData] yöntemi, iki olası biçime sahip bir sonuç döndürür:
- (devamı)
- {err: 0, data: [med1, med2, ...]} burada [medi] bir doktoru temsil eden bir nesnedir (unvan, ad, soyad),
- {err: n, messages: [msg1, msg2, ...]} burada [msgi] bir hata mesajıdır ve n, 0'dan farklıdır;
- 31. satır: görevin sözü döndürülür. Burada açıklanması gereken bir nokta var. İki sözümüz var:
- promise.then(): ilk bir söz olan [promise1]'i döndürür;
- return task.promise: ikinci bir söz olan [promise2]'i döndürür;
- sonuç olarak promise=promise.then(...; return task.promise), iki [promise2.promise1] sözünden oluşan bir zincirdir. [promise1], ancak [promise2] sözü elde edildiğinde, yani [dao.getData] görevi tamamlandığında değerlendirilecektir. [promise1] vaadi hiçbir asenkron göreve bağlı değildir. Dolayısıyla hemen elde edilecektir;
- 34-50. satırlar: Yukarıdaki açıklamadan, bu satırların ancak [dao.getData] görevi tamamlandığında yürütüleceği anlaşılmaktadır. 34. satırdaki fonksiyona geçirilen [result] parametresi, [dao.getData] yöntemi tarafından oluşturulur ve [task.resolve(result)] işlemi aracılığıyla çağıran koda iletilir; burada [result] şu biçimdedir:
- {err: 0, data: [med1, med2, ...]} burada [medi], bir doktoru temsil eden bir nesnedir (unvan, ad, soyad),
- {err: n, messages: [msg1, msg2, ...]} burada [msgi] bir hata mesajıdır ve n, 0'dan farklıdır;
- 37. satır: [result.err] hata koduna bakılır;
- 38-42. satırlar: Hata yoksa (result.err==0), doktor listesini alır ve görüntüleriz;
- 44-47. satırlar: tersine, hata varsa (result.err != 0), o zaman hata mesajları listesini alır ve ekrana yazarız;
- 53-56. satırlar: İki asenkron işlem tamamlanana kadar, iptal düğmesi içeren bekleme mesajı ekranda kalır. İptal anına göre neler olduğunu inceleyelim:
- öncelikle 19-50. satırların tek seferde yürütüldüğünü anlamak gerekir. Bu durumda, 25. satırdaki tek bir asenkron görev başlatılmıştır;
- bu ilk yürütmenin ardından V görünümü güncellenir ve dolayısıyla bekleme çubuğu ile iptal düğmesi görünür hale gelir. Kullanıcı, 25. satırdaki görev tamamlanmadan beklemeyi iptal ederse, 53. satırdaki yöntem yürütülür ve görev başarısızlıkla iptal edilir (55. satır);
- 56-59. satırlar: arayüz güncellenir: form yeniden görüntülenir ve geri kalan her şey gizlenir,
- ardından V görünümüne geri dönülür ve tarayıcı bir sonraki olayı işler. Görev sona erdiğinden, bu görevin vaadi alınır ve bu da bir olay oluşturur. Bu olay işlenir;
- ardından 28-32. satırlar yürütülür. Başarısızlık durumu için tanımlanmış bir işlev bulunmadığından, hiçbir kod yürütülmez. Yeni bir söz alınır; bu söz her zaman [promise.then] tarafından döndürülür ve her zaman alınır,
- olay işlendiği için V görünümüne geri dönülür ve tarayıcı bir sonraki olayı işleyecektir. 28. satırdaki [promise] işlendiği için, 34. satırdakisi de çözülecek ve bu da yeni bir olay yaratacaktır. Bu olay da işlenir;
- 34. satırda kullanılan söz yerine getirildiği için 34-49. satırlar sırayla çalıştırılacaktır. Yine, başarısızlık durumu için tanımlanmış bir işlev olmadığı için hiçbir kod çalıştırılmaz,
- böylece 50. satıra ulaşılır. Artık görev beklemesi yoktur ve yeni V görünümü görüntülenir;
- şimdi, ikinci asenkron görev [dao.getData] yürütülürken iptal işleminin gerçekleştiğini varsayalım. Önceki mantık burada da geçerlidir. Görevin sona ermesi, görev başarısızlıkla sonuçlandığında 34-50. satırların yürütülmesine neden olacaktır. Kısa süre sonra, [dao.getData] yönteminin web hizmetine asenkron bir HTTP çağrısı yaptığını göreceğiz. Bu çağrı iptal edilmeyecek, ancak sonucu kullanılmayacaktır.
V görünümünün görüntülenmesi ile tarayıcı olaylarının işlenmesi arasındaki bu sürekli gidip gelmeyi anlamak önemlidir. Olaylar, kullanıcı tarafından (bir tıklama) veya asenkron bir işlemin tamamlanması gibi sistem işlemleri tarafından tetiklenir. Tarayıcının dinlenme durumu, V görünümünün görüntülenmesidir. Tarayıcı, meydana gelen ve işlediği bir olay tarafından bu dinlenme durumundan çıkarılır. Olay işlendiği anda tarayıcı dinlenme durumuna geri döner. İşlenen olay, V görünümünün M modelini değiştirmişse, V görünümü güncellenir. Tarayıcı, bir sonraki olay tarafından dinlenme durumundan çıkarılır.
Her şey tek bir iş parçacığında gerçekleşir. İki olay asla eşzamanlı olarak işlenmez. Yürütülmeleri sıralıdır. Tarayıcı, bir sonraki olaya ancak bir önceki olay ona yol verdiğinde geçer; bu genellikle önceki olayın tamamen işlenmiş olması nedeniyle olur.
Açıklanması gereken bir nokta daha var. Hata mesajlarını görüntülemek için şunu yazıyoruz:
$scope.errors = { title: config.getMedecinsErrors, messages: utils.getErrors(result), show: true, model: {}};
Mesaj listesi, [utils] hizmetinde tanımlanan [utils.getErrors] yöntemi tarafından sağlanır. Bu yöntem şöyledir:
// sunucu yanıtındaki hataların analizi JSON
function getErrors(data) {
// veri {err:n, mesajlar:[]}, err!=0
// hatalar
var errors = [];
// hata kodu
var err = data.err;
switch (err) {
case 2 :
// yetkisiz
errors.push('not_authorized');
break;
case 3 :
// yasak
errors.push('forbidden');
break;
case 4 :
// yerel hata
errors.push('not_http_error');
break;
case 6 :
// belge bulunamadı
errors.push('not_found');
break;
default :
// diğer durumlar
errors = data.messages;
break;
}
// mesaj yoksa, bir mesaj eklenir
if (! errors || errors.length == 0) {
errors=['error_unknown'];
}
// hata listesi görüntülenir
return errors;
}
- 2-3. satırlar: Alınan [data] parametresi, iki özniteliğe sahip bir nesnedir:
- [err]: bir hata kodu;
- [messages]: bir mesaj listesi;
- 5. satır: Bir hata mesajları tablosu oluşturulacak. Bu mesajlar uluslararasılaştırılmıştır. Bu nedenle, 27. satır hariç olmak üzere, diziye mesajların kendisi değil, uluslararasılaştırma anahtarları eklenir. Bu durumda, [data] parametresinin [messages] özniteliği kullanılır. Bu mesajlar gerçek mesajlardır, mesaj anahtarları değildir. Ancak V görünümü bunları mesaj anahtarları olarak işleyecek ve bu anahtarlar bulunamayacaktır. Bu durumda, [translate] modülü bulamadığı mesaj anahtarını, yani burada gerçek bir mesajı görüntüler. Bu, istenen sonuçtur;
- 32-34. satırlar: [data.messages] modülünün 27. satırındaki değerin null olması durumunu ele alır. Bu, yazılan web hizmeti ile ortaya çıkar. Bu durumun önlenmesi gerekirdi.
3.7.6.4. [dao] hizmeti
![]() |
[dao] hizmeti, /JSON web hizmeti ile HTTP veri alışverişini sağlar. Kod şöyledir:
angular.module("rdvmedecins")
.factory('dao', ['$http', '$q', 'config', '$base64', 'utils',
function ($http, $q, config, $base64, utils) {
// günlükler
utils.debug("[dao] init");
// ----------------------------------özel yöntemler
// web hizmetinden veri alın
function getData(serverUrl, username, password, urlAction, info) {
// asenkron işlem
var task = $q.defer();
// URL isteği HTTP
var url = serverUrl + urlAction;
// temel kimlik doğrulama
var basic = "Basic " + $base64.encode(username + ":" + password);
// yanıt
var réponse;
// Tüm HTTP istekleri kimlik doğrulamasından geçmelidir
var headers = $http.defaults.headers.common;
headers.Authorization = basic;
// HTTP isteği gönderiliyor
var promise;
if (info) {
promise = $http.post(url, info, {timeout: config.timeout});
} else {
promise = $http.get(url, {timeout: config.timeout});
}
promise.then(success, failure);
// iptal edilebilmesi için görevin kendisini geri gönderiyoruz
return task;
// başarılı
function success(response) {
// response.data={status:0, data:[med1, med2, ...]} veya {status:x, data:[msg1, msg2, ...]
utils.debug("[dao] getData[" + urlAction + "] success réponse", response);
// yanıt
var payLoad = response.data;
réponse = payLoad.status == 0 ? {err: 0, data: payLoad.data} : {err: 1, messages: payLoad.data};
// yanıt veriliyor
task.resolve(réponse);
}
// hata
function failure(response) {
utils.debug("[dao] getData[" + urlAction + "] error réponse", response);
// durum analiz ediliyor
var status = response.status;
var error;
switch (status) {
case 401 :
// yetkisiz
error = 2;
break;
case 403:
// yasak
error = 3;
break;
case 404:
// bulunamadı
error = 6;
break;
case 0:
// yerel hata
error = 4;
break;
default:
// başka bir durum
error = 5;
}
// yanıt veriliyor
task.resolve({err: error, messages: [response.statusText]});
}
}
// --------------------- [dao] hizmet örneği
return {
getData: getData
}
}]);
- 77-79. satırlar: hizmetin tek bir alanı vardır: / JSON web hizmetinden bilgi almayı sağlayan [getData] yöntemi;
- 2. satır: Daha önce karşılaşmadığımız bir [$http] bağımlılığı görülmektedir. Bu, uzak bir varlıkla HTTP diyaloğunu sağlayan Angular'ın önceden tanımlanmış bir hizmetidir;
- 6. satır: kodun uygulamanın yaşam döngüsünün hangi aşamasında yürütüldüğünü görmek için bir günlük kaydı;
- 10. satır: [getData] yöntemi beş parametre kabul eder:
- [serverUrl]: web hizmetinin kök URL'i (http://localhost:8080);
- [urlAction]: İstenen belirli hizmetin URL'i (/getAllMedecins);
- [username]: kullanıcının kullanıcı adı;
- [password]: kullanıcının şifresi;
- [info]: talep edilen özel hizmetin URL kodu, bir POST işlemi yoluyla talep edildiğinde ek bilgileri içeren nesne. URL (/getAllMedecins) durumunda, bu parametre aktarılmamıştır. Dolayısıyla [undefined]'tir;
- 12. satır: Asenkron bir görev oluşturulur;
- 14. satır: istenen hizmetin (http://localhost:8080/getAllMedecins) URL'i tamamlanır;
- 16. satır: Kimlik doğrulama, aşağıdaki HTTP başlığı gönderilerek gerçekleştirilir:
burada [code], [username:password] dizesinin Base64 kodudur;
- satır, HTTP başlığının [Basic code] kısmını oluşturur;
- 18. satır: web hizmetinin yanıtı;
- 20. satır: Angular tarafından bir HTTP isteğinde varsayılan olarak gönderilen HTTP başlıkları, [$http.defaults.headers.common] nesnesinde tanımlanmıştır. [Authorization:Basic code] başlığı buna dahil değildir;
- 21. satır: Bu başlık, her zaman gönderilecek HTTP başlıklarına eklenir. Atamanın solunda, başlatılacak [Authorization] başlığı, sağında ise başlığın değeri bulunur; burada bu değer 16. satırda tanımlanmıştır. Dolayısıyla şöyle yazarsak:
Angular, HTTP başlığını gönderecektir:
- 23. satır: [$http] hizmetinin yöntemleri sözler döndürür. Bunlar [promise] değişkeninde saklanacaktır;
- 27. satır: Burada [info] parametresinin değeri [undefined] olduğu için 27. satır yürütülür. URL (http://localhost:8080/getAllMedecins) adresi, bir GET ile istenir. Çok uzun süre beklememek için, sunucudan yanıt almak üzere bir maksimum bekleme süresi (timeout) belirlenir. Varsayılan olarak bu süre bir saniyedir;
- 29. satır: söz konusu söz (promise) elde edildiğinde yürütülecek iki yöntem tanımlanır:
- [success]: 34. satırda tanımlanmıştır; görev başarıyla tamamlandığında sözün yerine getirilmesi durumunda yürütülecek yöntemdir;
- [failure]: 45. satırda tanımlanmıştır; görev başarısız olduğunda sözün elde edilmesi durumunda çalıştırılacak yöntemdir;
- her iki yöntem (aslında fonksiyon denmesi gerekir) [getData] fonksiyonu içinde tanımlanmıştır. Bu, JavaScript'te mümkündür. [getData]'te tanımlanan değişkenler, her iki iç fonksiyon olan [success, failure]'te de bilinir;
- 31. satır: 12. satırda oluşturulan görev döndürülür. Burada çağıran kodu hatırlamak gerekir:
promise = promise.then(function () {
// doktor listesi isteniyor;
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password, config.urlSvrMedecins);
return task.promise;
});
Yukarıdaki 3. satırda, bir görev başarıyla alınır.
- 34. satır: [success] işlevi, HTTP çağrısı başarıyla tamamlandığında, daha sonra yürütülür. Bu “başarı” kavramı, HTTP yanıtının ilk satırıyla ilgilidir. Bu yanıt şu şekildedir:
Kod, çağrının başarılı olup olmadığını belirten üç basamaklı bir metindir. Genel olarak, 2xx ve 3xx kodlarının başarı kodları olduğu, diğerlerinin ise hata kodları olduğu söylenebilir. Metin ise kısa bir açıklama metnidir. Aşağıda, biri başarı, diğeri hata durumunda olmak üzere iki olası yanıt örneği verilmiştir:
- 36. satır: Sunucunun yanıtı konsolda görüntülenir. [404 Not Found] hatasında, şuna benzer bir çıktı elde edilir:
[dao] getData[/getAllMedecins] error réponse : {"data":"...","status":404,"config":{...},"statusText":"Not Found"}
Bu yanıtta yalnızca [data], [status] ve [statusText] alanlarını kullanacağız.
- 38. satır: Yanıtın [data] alanını alırız. Bu alan aşağıdaki biçimlerden birinde olacaktır:
- {status: 0, data: [med1, med2, ...]} burada [medi] bir doktoru temsil eden bir nesnedir (unvan, ad, soyad),
- {status: n, data: [msg1, msg2, ...]} burada [msgi] bir hata mesajıdır ve n, 0'dan farklıdır;
![]() |

- 39. satır: {0,data} veya {n,messages} şeklinde yanıt oluşturulur. İlk yanıt, [data] alanında doktorları içerir. İkincisi ise sunucu tarafında meydana gelen bir hatayı bildirir. Sunucu bu hatayı işleyerek [err]'te bir hata kodu ve [data]'te bir hata mesajları listesi oluşturur. Her iki durumda da, HTTP emrinin tamamen işlendiğini belirten 200 numaralı HTTP kodunu döndürür. Bu nedenle her iki durum da aynı [success] işlevinde işlenir;
- 41. satır: görev tamamlandı ([task.resolve]) ve iki yanıtından biri döndürülür:
- {err: 0, data: [med1, med2, ...]} burada [medi] bir doktoru temsil eden bir nesnedir (unvan, ad, soyad),
- {err: n, messages: [msg1, msg2, ...]} burada [msgi] bir hata mesajıdır ve n, 0'dan farklıdır;
Bu kodu, denetleyicinin çağıran kodunda bu yanıtın nasıl alındığıyla ilişkilendirmek gerekir:
// önceki çağrının sonucunu analiz ediyoruz
promise.then(function (result) {
// sonuç={hata: 0, veri: [med1, med2, ...]}
// sonuç={hata: n, mesajlar: [msg1, msg2, ...]}
...
}
[task.resolve(réponse)] yanıtı, yukarıda [result] değişkeninde bulunur.
- 45. satır: Asenkron görev başarısızlıkla sonuçlandığında [failure] işlevi. İki olası durum vardır:
- sunucu, 2xx veya 3xx olmayan bir kod göndererek bu hatayı bildirir,
- Angular, HTTP çağrısını iptal eder. Bu durumda çağrı gerçekleşmez. Sunucu tarafından HTTP hata kodu döndürülmez, ancak bir Angular istisnası oluşur. Örneğin, çağrı yapılamayacak geçersiz bir URL sağlandığında bu durum söz konusudur;
- 46. satır: yanıt konsola görüntülenir;
- 48. satır: Sunucunun yanıtının şu biçimde olduğunu hatırlayalım:
{"data":"...","status":404,"config":{...},"statusText":"Not Found"}
- satırda, yukarıdaki [status] özniteliği alınır;
- satır 50-70: HTTP hata kodundan yola çıkarak, çağıran kodlardan HTTP yönteminin doğasını gizlemek için yeni bir hata kodu oluşturulur. Bu yöntemi kullanan denetleyicide, yöntem içinde bir HTTP çağrısı olduğuna dair hiçbir ipucu bulunmadığını doğrulayabiliriz;
- 51. satır: [401] hatası, kimlik doğrulamanın başarısız olmasına karşılık gelir (örneğin, yanlış şifre),
- 55. satır: [403] hatası, yetkisiz bir çağrıya karşılık gelir. Kullanıcı kimlik doğrulamasını başarıyla tamamlamıştır ancak talep ettiği URL için yeterli haklara sahip değildir. Bu durum, [user / user] kullanıcısında ortaya çıkacaktır. Bu kullanıcı veritabanında mevcut olmakla birlikte uygulamayı kullanma hakkına sahip değildir. Yalnızca [admin / admin] kullanıcısı bu hakka sahiptir;
- 59. satır: [404] hatası, URL'in bulunamamasına karşılık gelir. Bu hatanın birkaç nedeni olabilir:
- kullanıcı, hizmetin URL'sinde bir giriş hatası yapmıştır;
- web hizmeti başlatılmamış;
- web hizmeti yeterince hızlı yanıt vermedi (varsayılan bekleme süresi bir saniyedir);
- 63. satır: HTTP 0 hata kodu mevcut değil. Burada, kullanıcı tarafından girilen URL geçersiz olduğu ve çağrı yapılamadığı için Angular'ın istenen HTTP çağrısını yapmadığı durum söz konusudur. Daha sonra, Angular'ın istenen HTTP çağrısını yürütmemesi gereken başka durumlarla da karşılaşacağız;
- 72. satır: {err, messages} türünde bir yanıt döndürerek görev başarıyla tamamlanır (task.resolve); burada [messages] dizisi yalnızca [response.statusText] mesajından oluşur. Angular'ın istenen HTTP çağrısını yapmaması durumunda, boş bir dize elde edilir;
Artık uygulamanın hem genel hem de ayrıntılı bir görünümüne sahip olduğumuza göre, testlere başlayabiliriz.
3.7.6.5. Uygulama Testleri - 1
Geçerli girdilerle başlayalım:

![]() |
- [1]'te, bekleme olmaması için 0 giriyoruz;
- [2] örneğinde, girdiler doğru olmasına rağmen bir hata mesajı alıyoruz. Farklı hata mesajlarını burada göstermedik. [2] örneğinde görüntülenen mesaj, bir Angular istisnasına karşılık gelen hata kodu 0 ile ilişkili genel bir mesajdır. Angular, HTTP çağrısını yapmasını engelleyen bir sorunla karşılaştı. Bu gibi durumlarda, JavaScript konsolunun günlüklerine bakmak gerekir. Bunu yapmanın iki yolu vardır:
- Chrome tarayıcısında [F12] komutunu çalıştırmak;
- WebStorm konsolunu kullanmak;
Webstorm konsolunda, aşağıdakiler de dahil olmak üzere çeşitli mesajlar buluruz:
- 1. satır: Angular bir hata bildiriyor; buna daha sonra tekrar döneceğiz;
- 2. satır: [dao.getData] yönteminin günlüğü. Burada ilginç bazı bilgiler var:
- [status] değeri 0'dır; bu da HTTP yönteminin çağrılmadığını gösterir. Sonuç olarak [statusText] boş kalır,
- [url], [http://localhost:8080/getAllMedecins] ile aynıdır ve bu doğrudur;
- [Authorization":"Basic YWRtaW46YWRtaW4=] kimlik doğrulama başlığı olan HTTP de doğrudur;
Peki o zaman neden işe yaramadı? Günlüklerdeki anahtar ifade [No 'Access-Control-Allow-Origin' header is present]'tir. Bunu anlamak için uzun bir açıklama yapmak gerekiyor. Öncelikle istemci/sunucu uygulamasının genel mimarisine geri dönelim:

- Angular uygulamasının HTML / CSS / JS sayfaları, [1] sunucusundan geliyor;
- [2]'te, [dao] hizmeti başka bir sunucuya, yani [2] sunucusuna bir istek gönderir. Ancak bu, Angular uygulamasını çalıştıran tarayıcı tarafından yasaklanmıştır çünkü bu bir güvenlik açığıdır. Uygulama yalnızca geldiği sunucuya, yani [1] sunucusuna sorgu gönderebilir;
Aslında, tarayıcının Angular uygulamasının [2] sunucusuna sorgu göndermesini yasakladığını söylemek doğru değildir. Uygulama aslında bu sunucuya, kendisinden gelmeyen bir istemcinin kendisine sorgu göndermesine izin verip vermediğini sormaktadır. Bu paylaşım tekniğine CORS (Cross-Origin Resource Sharing) denir. [2] sunucusu, belirli HTTP başlıklarını göndererek onay verir. Burada, [2] sunucumuzun bu başlıkları göndermemesi nedeniyle tarayıcı, uygulama tarafından istenen HTTP çağrısını gerçekleştirmeyi reddetti.
Şimdi ayrıntılara girelim. HTTP çağrısı sırasında gerçekleşen ağ iletişimini inceleyelim. Bunun için Chrome tarayıcısında [F12] tuşlarına basarak geliştirici araçlarını açıyoruz ve ağ iletişimini görmek için [Network] sekmesini seçiyoruz:
![]() |
- [1] adresinde, [network] sekmesini seçiyoruz;
- [2]'te doktor listesini talep ediyoruz;
[network] sekmesinde şu bilgileri elde ediyoruz:
![]() |
- [1] sekmesinde, sunucuya gönderilen bilgiler;
- [2]'te, sunucunun yanıtı;
[1]'te, tarayıcının istenen URL için HTTP ve [OPTIONS] isteklerini gönderdiği görülebilir. [OPTIONS], daha çok bilinen [GET] ve [POST] ile birlikte kullanılabilen komutlardan biridir. Bu komut, bir sunucudan özellikle desteklediği HTTP seçenekleri hakkında bilgi talep etmeyi sağlar; komutun adı da buradan gelmektedir. Sunucu, yanıtını [2] biçiminde verir. Kendi etki alanı dışındaki istemcilerin isteklerini kabul ettiğini belirtmek için, [Access-Control-Allow-Origin] adlı özel bir başlık göndermesi gerekir. Ve sunucu bu başlığı geri göndermediği için Angular, istenen HTTP çağrısını yürütemedi ve şu hatayı verdi:
XMLHttpRequest cannot load http://localhost:8080/getAllMedecins. İstenen kaynakta 'Access-Control-Allow-Origin' başlığı bulunmamaktadır. Bu nedenle 'http://localhost:63342' kaynaklı erişime izin verilmemektedir.
Bu nedenle, sunucumuzu, beklenen HTTP başlığını gönderecek şekilde değiştirmeliyiz.
3.7.6.6. Web sunucusunun değiştirilmesi / JSON
Eclipse'e geri dönüyoruz. Yaptığımız değişiklikleri korumak için, web sunucusunun mevcut sürümünü / JSON [rdvmedecins-webapi-v2]'ten [rdvmedecins-webapi-v3] [1]'e kopyalıyoruz:
![]() |
Web hizmetinin yapılandırma öğelerinden biri olan [ApplicationModel] dosyasında ilk değişikliği yapıyoruz:
package rdvmedecins.web.models;
...
@Component
public class ApplicationModel implements IMetier {
// [métier] katmanı
@Autowired
private IMetier métier;
// [métier] katmanından gelen veriler
private List<Medecin> médecins;
private List<Client> clients;
private List<String> messages;
// yapılandırma verileri
private boolean CORSneeded = true;
...
public boolean isCORSneeded() {
return CORSneeded;
}
}
- 17. satır: Sunucu etki alanı dışındaki istemcileri kabul edip etmediğimizi belirten bir boole değişkeni oluşturuyoruz;
- 21-23. satırlar: bu bilgiye erişim yöntemi;
Ardından yeni bir Spring denetleyicisi oluşturuyoruz: MVC [3]:
![]() |
[RdvMedecinsCorsController] sınıfı şu şekildedir:
package rdvmedecins.web.controllers;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import rdvmedecins.web.models.ApplicationModel;
@Controller
public class RdvMedecinsCorsController {
@Autowired
private ApplicationModel application;
// seçeneklerin istemciye gönderilmesi
private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// başlık belirleniyor CORS
response.addHeader("Access-Control-Allow-Origin", "*");
}
}
// doktor listesi
@RequestMapping(value = "/getAllMedecins", method = RequestMethod.OPTIONS)
public void getAllMedecins(HttpServletResponse response) {
sendOptions(response);
}
}
- 28-31. satırlar: URL [/getAllMedecins] komutuyla çağrıldığında HTTP [OPTIONS] denetleyicisini tanımlar;
- 29. satır: [getAllMedecins] yöntemi, isteği yapan istemciye gönderilecek olan [HttpServletResponse] nesnesini parametre olarak alır. Bu nesne Spring tarafından enjekte edilir;
- satır 30: isteğin işlenmesi, 19-25. satırlardaki özel yönteme devredilir;
- satır 15-16: [ApplicationModel] nesnesi enjekte edilir;
- 20-23. satırlar: Sunucu, kendi etki alanı dışındaki istemcileri kabul edecek şekilde yapılandırılmışsa, HTTP başlığı gönderilir:
Access-Control-Allow-Origin: *
bu, sunucunun tüm etki alanlarından (*), yani her türlü etki alanından gelen istemcileri kabul ettiği anlamına gelir.
Artık yeni testlere hazırız. Web hizmetinin yeni sürümünü başlatıyoruz ve sorunun hâlâ devam ettiğini görüyoruz. Hiçbir şey değişmemiştir. Yukarıdaki 30. satıra bir konsol çıktısı eklesek bile, bu çıktı hiçbir zaman görüntülenmez; bu da 29. satırdaki [getAllMedecins] yönteminin hiçbir zaman çağrılmadığını gösterir.
Biraz araştırma yaptıktan sonra, Spring MVC'in HTTP ve [OPTIONS] komutlarını varsayılan bir işleme tabi tutarak kendisi işlediğini keşfediyoruz. Dolayısıyla yanıtı her zaman Spring veriyor ve 29. satırdaki [getAllMedecins] yöntemi hiçbir zaman çağrılmıyor. Spring MVC’in bu varsayılan davranışı değiştirilebilir. Yeni davranışı yapılandırmak için yeni bir yapılandırma sınıfı ekliyoruz:
![]() |
Yeni yapılandırma sınıfı [WebConfig] şu şekildedir:
package rdvmedecins.web.config;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
// CORS başlıkları için dispatcherservlet yapılandırması
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet = new DispatcherServlet();
servlet.setDispatchOptionsRequest(true);
return servlet;
}
}
- 8. satır: Sınıf, bir Spring yapılandırma sınıfıdır. Spring bağlamına yerleştirilecek bean'leri tanımlar;
- 12. satır: [dispatcherServlet] bean'i, istemcilerin isteklerini yöneten servleti tanımlamak için kullanılır. Bu servlet, [DispatcherServlet] türündedir. Bu servlet normalde varsayılan olarak oluşturulur. Kendimiz oluşturursak, onu yapılandırabiliriz;
- 14. satır: [DispatcherServlet] türünde bir örnek oluşturulur;
- 15. satır: servletin, HTTP ve [OPTIONS] komutlarını uygulamaya iletmesini talep ediyoruz;
- 16. satır: Servlet'i bu şekilde yapılandırıyoruz;
Geriye [AppConfig] sınıfını değiştirmek kalıyor:
package rdvmedecins.web.config;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import rdvmedecins.config.DomainAndPersistenceConfig;
@EnableAutoConfiguration
@ComponentScan(basePackages = { "rdvmedecins.web" })
@Import({ DomainAndPersistenceConfig.class, SecurityConfig.class, WebConfig.class })
public class AppConfig {
}
- 11. satır: Yeni yapılandırma sınıfı [WebConfig] içe aktarılır;
3.7.6.7. Uygulama Testleri - 2
Web hizmetinin yeni sürümünü / JSON başlatıyoruz ve Angular istemcimizle doktor listesini almaya çalışıyoruz. [Network] sekmesinde ağ iletişimini inceliyoruz:
![]() |
- [1]'te, HTTP [Access-Control-Allow-Origin: *] başlığının artık sunucu yanıtında yer aldığı görülebilir. Ancak yine de çalışmıyor. [2] dosyasındaki konsol günlüklerini inceliyoruz. Burada şu günlüğü görüyoruz:
XMLHttpRequest cannot load http://localhost:8080/getAllMedecins. İstek başlığı alanı Authorization, Access-Control-Allow-Headers tarafından izin verilmiyor
Görüldüğü üzere, tarayıcı, kimlik doğrulama başlığını kendisine gönderebileceğimizi belirten yeni bir başlık bekliyor: HTTP [Access-Control-Allow-Headers]:
Bu olumlu bir işaret olabilir. Angular, HTTP GET komutunu göndermek istemiş olabilir. Ancak bu komut bir kimlik doğrulama başlığıyla birlikte geldiği için, sunucunun bunu kabul edip etmediğini soruyor.
Web sunucumuzu / JSON bu başlığı gönderecek şekilde değiştiriyoruz. [RdvMedecinsCorsController] sınıfı şu şekilde değişiyor:
// seçeneklerin istemciye gönderilmesi
private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// CORS başlığı ayarlanıyor
response.addHeader("Access-Control-Allow-Origin", "*");
// [Authorization] başlığına izin veriliyor
response.addHeader("Access-Control-Allow-Headers", "Authorization");
}
- 6-7. satırlarda eksik olan başlık eklenir.
Sunucuyu yeniden başlatıyoruz ve Angular istemcisiyle doktor listesini tekrar istiyoruz:
![]() |
Bu sefer başarılı oldu. Konsol günlükleri, [dao.getData] yöntemi tarafından alınan yanıtı gösteriyor:
[dao] getData[/getAllMedecins] success réponse : {"data":{"status":0,"data":[{"id":1,"version":1,"titre":"Mme","nom":"PELISSIER","prenom":"Marie"},{"id":2,"version":1,"titre":"Mr","nom":"BROMARD","prenom":"Jacques"},{"id":3,"version":1,"titre":"Mr","nom":"JANDOT","prenom":"Philippe"},{"id":4,"version":1,"titre":"Melle","nom":"JACQUEMOT","prenom":"Justine"}]},"status":200,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"timeout":1000,"url":"http://localhost:8080/getAllMedecins","headers":{"Accept":"application/json, text/plain, */*","Authorization":"Basic YWRtaW46YWRtaW4="}},"statusText":"OK"}
Görüldüğü üzere:
- sunucu, [status=200] hata kodunu [statusText=OK] mesajıyla birlikte geri gönderdi. Bu nedenle şu anda [success] işlevindeyiz;
- sunucu, iki alan içeren bir [data] nesnesi döndürmüştür:
- [status]: (HTTP ve [status] hata kodlarıyla karıştırılmamalıdır). Burada [status=0], URL ve [/getAllMedecins]'in hatasız bir şekilde işlendiğini gösterir;
- [data]: JSON numaralı doktor listesini içerir;
Şimdi diğer ilginç örnekleri inceleyelim:
[login, password] kimlik numaralarında hata var:
![]() |
Uygulamaya erişimi olmayan [user / user] kimliğiyle oturum açıldığında (uygulamaya yalnızca [admin] erişebilir):
![]() |
Bu sefer hata artık [Erreur d'authentification] değil, [Accès refusé]'tir.
3.7.7. Örnek 7: Müşteri listesi
Bu sefer, önceki uygulamayı kullanarak müşteri listesini [Bootstrap select] türünde bir açılır listede sunacağız (bkz. paragraf 3.6.6).
3.7.7.1. V görünümü
Başlangıç görünümü şu şekilde olacaktır:
![]() |
V görünümünü elde etmek için, [app-16.html] kodunu [app-17.html] olarak kopyalayıp aşağıdaki şekilde değiştiriyoruz:
<div class="container" >
<h1>Rdvmedecins - v1</h1>
<!-- bekleme mesajı -->
<div class="alert alert-warning" ng-show="waiting.visible" >
...
</div>
<!-- istek -->
<div class="alert alert-info" ng-hide="waiting.visible" >
...
<button class="btn btn-primary" ng-click="execute()">{{clients.title|translate}}</button>
</div>
<!-- müşteri listesi -->
<div class="row" style="margin-top: 20px" ng-show="clients.show">
<div class="col-md-3">
<h2 translate="{{clients.title}}"></h2>
<select data-style="btn-primary" class="selectpicker">
<option ng-repeat="client in clients.data" value="{{client.id}}">
{{client.titre}} {{client.prenom}} {{client.nom}}
</option>
</select>
</div>
</div>
<!-- hata listesi -->
<div class="alert alert-danger" ng-show="errors.show">
...
</div>
</div>
....
<script type="text/javascript" src="rdvmedecins-05.js"></script>
- 5-7. satırlar: bekleme çubuğu değişmez;
- 10-13. satırlar: Düğme metni (12. satır) hariç, form değişmez;
- 28-30. satırlar: hata başlığı değişmez;
- 16-25. satırlar: Müşteriler, [Bootstrap-selectpicker] bileşeni tarafından stil verilen bir açılır listede görüntülenir (data-style, class öznitelikleri, 19. satır);
- 20. satır: açılır listenin çeşitli seçeneklerini oluşturmak için [ng-repeat] yönergesi kullanılır. Bir seçeneğin etiketinin [Mme Julienne Tatou] türünde, seçeneğin değerinin ise [100] türünde olduğu ve burada 100'ün görüntülenen müşterinin id kimliği olduğu unutulmamalıdır;
- 34. satır: JavaScript kodu yeni bir dosya olan [rdvmedecins-05]'e taşınır;
3.7.7.2. C denetleyicisi ve M modeli
[rdvmedecins-05] dosyasındaki JavaScript kodu, [rdvmedecins-04] dosyasından kopyalanarak elde edilir:

Neredeyse hiçbir şey değişmez, ancak artık müşteri listesini sağlamak üzere uyarlanmış olan denetleyici hariç:
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao', '$translate',
function ($scope, utils, config, dao, $translate) {
// ------------------- şablon başlatma
// şablon
$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time: undefined};
$scope.waitingTimeText = config.waitingTimeText;
$scope.server = {url: undefined, login: undefined, password: undefined};
$scope.clients = {title: config.listClients, show: false, model: {}};
$scope.errors = {show: false, model: {}};
$scope.urlServerLabel = config.urlServerLabel;
$scope.loginLabel = config.loginLabel;
$scope.passwordLabel = config.passwordLabel;
// asenkron görev
var task;
// eylemin yürütülmesi
$scope.execute = function () {
// UI güncelleniyor
$scope.waiting.visible = true;
$scope.clients.show = false;
$scope.errors.show = false;
// simüle edilmiş bekleme
task = utils.waitForSomeTime($scope.waiting.time);
var promise = task.promise;
// bekleme
promise = promise.then(function () {
// müşteri listesi isteniyor;
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password, config.urlSvrClients);
return task.promise;
});
// önceki çağrının sonucu analiz ediliyor
promise.then(function (result) {
// sonuç={hata: 0, veriler: [client1, client2, ...]}
// sonuç={hata: n, mesajlar: [msg1, msg2, ...]}
if (result.err == 0) {
// toplanan veriler modele eklenir
$scope.clients.data = result.data;
// UI güncellenir
$scope.clients.show = true;
$scope.waiting.visible = false;
// açılır listeye stil verilir
$('.selectpicker').selectpicker();
} else {
// müşteri listesini alırken hatalar oluştu
$scope.errors = { title: config.getClientsErrors, messages: utils.getErrors(result), show: true, model: {}};
// UI güncelleniyor
$scope.waiting.visible = false;
}
});
};
// iptal bekleniyor
function cancel() {
// görev tamamlanıyor
task.reject();
// UI güncelleniyor
$scope.waiting.visible = false;
$scope.clients.show = false;
$scope.errors.show = false;
}
}
])
;
- Denetleyicide çok az değişiklik var. Eskiden doktor listesi sunuyordu. Artık müşteri listesi sunuyor;
- 9. satır: [$scope.clients], V görünümündeki müşteri başlık çubuğunun şablonu olacaktır;
- 30. satır: Artık URL ve [/getAllClients] kullanılmaktadır;
- 35-36. satırlar: [dao.getData] yöntemi tarafından döndürülen iki yanıt biçimi. Artık doktorlar yerine müşteriler var;
- 44. satır: Angular kodunda oldukça nadir görülen bir komut. DOM (Belge Nesne Modeli) doğrudan işleniyor. Burada, [selectpicker] ([bootstrap-select.min.js]'in bir parçasıdır) yöntemini, [selectpicker] ve [$('.selectpicker')] sınıflarına sahip DOM öğelerine uygulamak istiyoruz. Bunlardan sadece biri var, o da açılır liste:
<select data-style="btn-primary" class="selectpicker" select-enable="">
....
</select>
3.6.6 numaralı paragrafta, bunun açılır menüyü şu şekilde biçimlendirdiği gösterilmiştir:
![]() | ![]() |
Doktorlar için yapıldığı gibi, web hizmetinde de değişiklik yapmamız gerekiyor.
3.7.7.3. Web hizmetinde yapılan değişiklik - 1
![]() |
[RdvMedecinsController] sınıfına yeni bir yöntem eklenmiştir:
package rdvmedecins.web.controllers;
...
@Controller
public class RdvMedecinsCorsController {
@Autowired
private ApplicationModel application;
// seçenekler müşteriye gönderiliyor
private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// başlık ayarlanıyor CORS
response.addHeader("Access-Control-Allow-Origin", "*");
// başlığı onaylanıyor [Authorization]
response.addHeader("Access-Control-Allow-Headers", "Authorization");
}
}
// doktor listesi
@RequestMapping(value = "/getAllMedecins", method = RequestMethod.OPTIONS)
public void getAllMedecins(HttpServletResponse response) {
sendOptions(response);
}
// müşteri listesi
@RequestMapping(value = "/getAllClients", method = RequestMethod.OPTIONS)
public void getAllClients(HttpServletResponse response) {
sendOptions(response);
}
}
- 29-32. satırlar: [getAllClients] yöntemi, tarayıcı tarafından kendisine gönderilecek olan HTTP ve [OPTIONS] isteklerini işleyecektir;
3.7.7.4. Uygulama Testleri – 1
Artık test yapmaya hazırız. Web sunucusunu başlatıp Angular formuna geçerli değerler giriyoruz. Aşağıdaki yanıtı alıyoruz:

Bu hata mesajı, Angular’ın istenen HTTP isteğini gerçekleştiremediğinde görüntülenir. Bu durumda, sorunun nedenlerini konsol günlüklerinde aramak gerekir. Günlüklerde şu mesaj bulunur:
XMLHttpRequest cannot load http://localhost:8080/getAllClients. İstenen kaynakta 'Access-Control-Allow-Origin' başlığı bulunmamaktadır. Bu nedenle 'http://localhost:63342' kaynaklı erişime izin verilmez.
Çözüldüğü sanılan bir sorun. Şimdi, gerçekleşen ağ iletişimini inceleyelim:

[getAllClients] işleminin, HTTP ve [OPTIONS] yöntemleriylesorunsuz bir şekilde tamamlandığını, ancak [getAllClients] işleminin HTTP ve [GET] yöntemleriyle iptal edildiğini görüyoruz. [OPTIONS] isteğine verilen yanıt şuydu:

CORS'in HTTP başlıkları mevcut. Şimdi GET sırasında gerçekleşen HTTP alışverişlerini inceleyelim:

HTTP isteği doğru görünüyor. Özellikle kimlik doğrulama başlığı göze çarpıyor.
Önceki hata mesajının yanı sıra, konsol günlüklerinde şu mesaj da yer alıyor:
[dao] getData[/getAllClients] error réponse : {"data":"","status":0,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"timeout":1000,"url":"http://localhost:8080/getAllClients","headers":{"Accept":"application/json, text/plain, */*","Authorization":"Basic YWRtaW46YWRtaW4="}},"statusText":""}
Bu, [dao.getData] yöntemi, HTTP isteğine yanıt aldığında sistematik olarak oluşturduğu günlüktür. Burada iki husus dikkat çekmektedir:
- [status=0]: Bu, HTTP isteğinin Angular tarafından iptal edildiği anlamına gelir;
- [method=GET]: ve iptal edilen istek GET'tir;
İlk mesajla birleştirildiğinde, bu, GET isteği için de Angular'ın burada CORS başlıklarını beklediği anlamına gelir. Oysa şu anda web hizmetimiz bunları yalnızca HTTP ve [OPTIONS] istekleri için gönderiyor. Bu hatanın şimdi ortaya çıkması ve doktor listesi için çıkmaması çok garip. Bunun için bir açıklamam yok.
Bu nedenle web hizmetinde yeniden değişiklik yapmak gerekiyor.
3.7.7.5. Web hizmetinde değişiklik – 2
![]() |
[GET] ve [POST] yöntemleri, [RdvMedecinsController] sınıfında işlenmektedir. Bu yöntemlerin CORS başlıklarını göndermesi için bu sınıfı değiştirmemiz gerekiyor. Bunu şu şekilde yapıyoruz:
@RestController
public class RdvMedecinsController {
@Autowired
private ApplicationModel application;
@Autowired
private RdvMedecinsCorsController rdvMedecinsCorsController;
...
// müşteri listesi
@RequestMapping(value = "/getAllClients", method = RequestMethod.GET)
public Reponse getAllClients(HttpServletResponse response) {
// başlıklar CORS
rdvMedecinsCorsController.getAllClients(response);
// uygulama durumu
if (messages != null) {
return new Reponse(-1, messages);
}
// müşteri listesi
try {
return new Reponse(0, application.getAllClients());
} catch (Exception e) {
return new Reponse(1, Static.getErreursForException(e));
}
}
...
- 8. satır: [RdvMedecinsCorsController] denetleyicisine yerleştirdiğimiz kodu yeniden kullanmak istiyoruz. Bu nedenle bu kodu buraya ekliyoruz;
- 14. satır: İsteği işleyen yöntem [GET /getAllClients]. İki değişiklik yapıyoruz:
- 14. satır: [HttpServletResponse] nesnesini yöntemin parametrelerine enjekte ediyoruz,
- 16. satır: [RdvMedecinsCorsController] sınıfının yöntemlerini kullanarak bu nesneye CORS başlıklarını ekliyoruz;
3.7.7.6. Uygulama Testleri – 2
Web hizmetinin yeni sürümünü başlatıyoruz ve müşteri listesini yeniden istiyoruz. Aşağıdaki yanıtı alıyoruz:
![]() |
- [1]'te bir yanıt var, ancak bu yanıt boş; [2];
- [3]'te: ağ iletişimi sorunsuz gerçekleşti;
Konsol günlüklerinde, [dao.getData] yöntemi aldığı yanıtı görüntüledi:
[dao] getData[/getAllClients] success réponse : {"data":{"status":0,"data":[{"id":1,"version":1,"titre":"Mr","nom":"MARTIN","prenom":"Jules"},{"id":2,"version":1,"titre":"Mme","nom":"GERMAN","prenom":"Christine"},{"id":3,"version":1,"titre":"Mr","nom":"JACQUARD","prenom":"Jules"},{"id":4,"version":1,"titre":"Melle","nom":"BISTROU","prenom":"Brigitte"}]},"status":200,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"timeout":1000,"url":"http://localhost:8080/getAllClients","headers":{"Accept":"application/json, text/plain, */*","Authorization":"Basic YWRtaW46YWRtaW4="}},"statusText":"OK"}
Dolayısıyla yöntem, müşteri listesini başarıyla almıştır. Kod doğrulandıktan sonra, pek iyi anlamadığımız şu komuttan şüphelenmeye başlıyoruz:
// açılır listeyi biçimlendir
$('.selectpicker').selectpicker();
2. satırı yorum satırına alıyoruz ve tekrar deniyoruz. Bunun üzerine şu yanıtı alıyoruz:
![]() |
Böylece sorunu tespit ettik. Sorun, açılır menüye [selectpicker] yönteminin uygulanmasından kaynaklanıyor. Hatalı sayfanın kaynak koduna baktığımızda şunu görüyoruz:
![]() |
- [1]'te açılır listenin öğeleriyle birlikte mevcut olduğu, ancak görüntülenmediği görülüyor [style='display:none'];
- [2]'te ise [bootstrap select] düğmesinin görüntülendiği görülüyor. Açılır menünün öğeleri <ul role='menu'> listesinde görünmelidir. Ancak orada yoklar ve bu nedenle liste boş görünüyor. Görünüşe göre, [selectpicker] yöntemi açılır menüye uygulandığında, o anda içeriği boştu;
Çözüm bulmak için internette araştırma yaparken şuna rastladık. Şu kodu değiştiriyoruz:
// açılır listeye stil uygulanıyor
$('.selectpicker').selectpicker();
şununla değiştiriyoruz:
// açılır listeye stil uygulanıyor
$timeout(function(){
$('.selectpicker').selectpicker();
});
[bootstrap-select] stili, [$timeout] işlevi aracılığıyla uygulanır. Bu işlevi daha önce de görmüştük; belirli bir gecikme süresinden sonra bir işlevi çalıştırmaya yarar. Burada gecikme süresinin belirtilmemiş olması, gecikme süresinin sıfır olduğu anlamına gelir. Yukarıdaki satırlar, bir olayı tarayıcının olay kuyruğuna ekler. Mevcut olayın işlenmesi ([Liste des clients] düğmesine tıklama) tamamlandığında, V görünümü görüntülenecektir. Hemen ardından tarayıcı, olay listesini kontrol edecektir. Gecikme süresi sıfır olduğu için [$timeout] olayı listenin en başında yer alacak ve işlenecektir. Ardından, [bootstrap-select] stili, doldurulmuş bir açılır listeye uygulanacaktır. Sonucu görelim:
![]() |
Görüntülenen sayfanın kaynak koduna tekrar bakarsak, şunu görürüz:
![]() |
Daha önce boş olan [bootstrap-select] düğmesi artık müşteri listesini içeriyor.
3.7.7.7. Bir yönergenin kullanımı
V görünümünün C denetleyicisinde şu kodu gördük:
// açılır listeye stil uygulanıyor
$('.selectpicker').selectpicker();
Burada bir DOM nesnesi üzerinde işlem yapıyoruz. Birçok Angular geliştiricisi, denetleyici kodunda DOM ile işlem yapmaktan hoşlanmaz. Onlara göre bu işlem bir yönerge içinde yapılmalıdır. Bir Angular direktifi, HTML dilinin bir uzantısı olarak görülebilir. Böylece yeni HTML öğeleri veya öznitelikleri oluşturmak mümkündür. İlk örneği inceleyelim:
Aşağıdaki JS [selectEnable] dosyasını oluşturuyoruz:
angular.module("rdvmedecins").directive('selectEnable', ['$timeout', function ($timeout) {
return {
link: function (scope, element, attrs) {
$timeout(function () {
var selectpicker = $('.selectpicker');
selectpicker.selectpicker();
});
}
};
}]);
- Yönerge, artık alıştığımız denetleyici sözdizimini takip ediyor:
angular.module("rdvmedecins").directive('selectEnable', ['$timeout', function ($timeout)
Bu yönerge, [rvmedecins] modülüne aittir. Bu, iki parametre kabul eden bir işlevdir:
- (devamı)
- ilki, [selectEnable] yönergesinin adıdır;
- ikincisi ise ['obj1','obj2',..., function(obj1, obj2,...)] adlı bir dizidir; burada [obj] değerleri, işlev içine eklenecek nesnelerdir. Burada eklenen tek nesne, önceden tanımlanmış [$timeout] nesnesidir;
- [directive] işlevi, çeşitli özniteliklere sahip olabilen bir nesne döndürür. Burada tek öznitelik, [link] özniteliğidir (3. satır). Bu özniteliğin değeri, üç parametre alan bir işlevdir:
- scope: yönergenin kullanıldığı görünüm şablonu;
- element: görünüm öğesi, yönergenin nesnesi;
- attrs: bu öğenin öznitelikleri;
Bir örnek verelim. [selectEnable] yönergesi aşağıdaki bağlamda kullanılabilir:
Yukarıda, [select-enable] özniteliği, HTML <div> öğesine [selectEnable] yönergesini uygular. Bir [doSomething] yönergesi, herhangi bir HTML öğesine [do-something] özniteliği eklenerek uygulanabilir. Yönergenin adı ile ona ilişkin öznitelik arasındaki yazım değişikliğine dikkat edilmelidir. [camelCase] yazımından [camel-case] yazımına geçilir.
[selectEnable] yönergesi de şu şekilde kullanılabilir:
Burada [doSomething] yönergesi, HTML <do-something> etiketinin bir parçası olarak uygulanır.
Yazım biçimine geri dönelim
yazımına ve yönergenin [link] işlevinin üç parametresine, [scope, element, attrs]'e dönelim:
- scope: <div> öğesinin bulunduğu görünüm şablonudur;
- element: <div> etiketi kendisidir;
- attrs: <div> etiketinin öznitelik tablosudur. Bunlar, yönergeye bilgi aktarmak için kullanılabilir. Yukarıda, [data] bilgisini elde etmek için attrs['selectEnable'] yazılacaktır. [selectEnable] yazımındaki değişikliğin, [select-enable] özniteliğini belirtmek için yapıldığına dikkat edilmelidir;
Yönergenin koduna geri dönelim:
angular.module("rdvmedecins").directive('selectEnable', ['$timeout', function ($timeout) {
return {
link: function (scope, element, attrs) {
$timeout(function () {
$('.selectpicker').selectpicker();
});
}
};
}]);
- 14-16. satırlar: Daha önce denetleyiciye yerleştirdiğimiz kodu burada da görüyoruz. Bu kod, V görünümünün görüntülenmesi sırasında [select-enable] yönergesiyle (öğe veya öznitelik biçiminde) karşılaşıldığında yürütülür.
Bu yönergeyi uygulamak için, [app-17.html] dosyasını [app-17B.html] dosyasına kopyalayıp şu şekilde değiştiriyoruz:
<select data-style="btn-primary" class="selectpicker" select-enable="">
<option ng-repeat="client in clients.data" value="{{client.id}}">
{{client.titre}} {{client.prenom}} {{client.nom}}
</option>
</select>
- 1. satır: [selectEnable] yönergesini HTML [select] öğesine uygularız. Yönergeye aktarılacak herhangi bir bilgi olmadığından, sadece [select-enable=""] yazıyoruz;
Ayrıca denetleyiciyi, JS [rdvmedecins-05.js] dosyasını [rdvmedecins-05B.js] olarak kopyalayarak değiştiriyoruz ve yeni JS dosyasını [app-17B.html] dosyasına ve [selectEnable.js] yönerge dosyasına referans veriyoruz. Bu son noktayı unutmamak gerekir. Yönerge dosyası yoksa, [select-enable=""] özniteliği işlenmeyecek, ancak Angular herhangi bir hata bildirmeyecektir.
<script type="text/javascript" src="rdvmedecins-05B.js"></script>
<script type="text/javascript" src="selectEnable.js"></script>
JS ve [rdvmedecins-05B.js] dosyalarında, denetleyiciden aşağıdaki satırları siliyoruz:
// açılır listeye stil uygulanıyor
$timeout(function(){
$('.selectpicker').selectpicker();
});
çünkü bu işlem artık direktif tarafından gerçekleştiriliyor.
3.7.7.8. Uygulama Testleri – 3
Yeni [app-17B.html] uygulamasını test ettiğimizde şu sonucu elde ediyoruz:
![]() |
- [1]'te ise boş bir liste elde edilir.
Konsol günlükleri şunu gösterir:
- 1. satır: [dao] hizmetinin başlatılması;
- 2. satır: V görünümünün ilk görüntülenmesinde, [selectEnable] yönergesi yürütülür;
- satır 3: kullanıcı [Liste des clients] düğmesine tıkladığında bu satır görünür. Burada, [selectEnable] yönergesinin ikinci kez yürütülmediği görülür. Sonuç olarak, bu yönerge müşteri listesi boşken yürütülmüştür ve bu nedenle açılır liste boş kalmıştır;
Başka bir deyişle, işlemi:
$('.selectpicker').selectpicker();
doğru zamanda gerçekleşmemiştir. Bu sorunu çeşitli şekillerde çözmeye çalışabiliriz. Birçok başarısız denemenin ardından, yukarıdaki işlemin yalnızca bir kez ve açılır liste doldurulduğunda gerçekleştirilmesi gerektiği anlaşılır. Bu sonucu elde etmek için, <select> etiketini şu şekilde yeniden yazıyoruz:
<select data-style="btn-primary" class="selectpicker" select-enable="" ng-if="clients.data">
<option ng-repeat="client in clients.data" value="{{client.id}}">
{{client.titre}} {{client.prenom}} {{client.nom}}
</option>
</select>
1. satırda, <select> etiketi yalnızca [clients.data] varsa oluşturulur. V görünümünün ilk görüntülenmesinde bu durum söz konusu değildir. Dolayısıyla <select> etiketi oluşturulmayacak ve [selectEnable] yönergesi değerlendirilmeyecektir. Kullanıcı [Liste des clients] düğmesine tıkladığında, M şablonunda [clients.data] yeni bir değer alacaktır. M şablonu değiştiği için <select> etiketi yeniden değerlendirilecek ve bu durumda oluşturulacaktır. Dolayısıyla [selectEnable] yönergesi de değerlendirilecektir. Bu yönerge değerlendirildiğinde, <select> etiketinin 2-4. satırları henüz değerlendirilmemiştir. Dolayısıyla boş bir müşteri listesi elde edilir. [selectEnable] yönergesini şu şekilde yazarsak:
angular.module("rdvmedecins").directive('selectEnable', ['$timeout', 'utils', function ($timeout, utils) {
return {
link: function (scope, element, attrs) {
utils.debug("directive selectEnable");
$('.selectpicker').selectpicker();
}
}
}]);
5. satır boş bir listeyle çalıştırılacak ve ekranda boş bir açılır liste görünecektir. Bu durumda şu şekilde yazılmalıdır:
angular.module("rdvmedecins").directive('selectEnable', ['$timeout', 'utils', function ($timeout, utils) {
return {
link: function (scope, element, attrs) {
utils.debug("directive selectEnable");
$timeout(function () {
$('.selectpicker').selectpicker();
})
}
}
}]);
yazmak gerekir. 5. satırdaki [$timeout] nedeniyle, 6. satır ancak V görünümünün tamamen değerlendirilmesinden sonra, yani <select> etiketinin tüm öğelerini içerdiği bir anda çalıştırılacaktır.
3.7.8. Örnek 8: Bir doktorun randevu takvimi
Şimdi bir doktorun randevu takvimini görüntüleyen bir uygulama sunacağız.
3.7.8.1. Uygulamanın V görünümü
Aşağıdaki formu sunacağız:
![]() |
- [1]'te, 25 Haziran 2014 [3] tarihinde Bayan PELISSIER [2]'in randevu takvimi isteniyor;
Aşağıdaki [4] sonucunu elde ediyoruz:
![]() |
İki görünümü ayrı ayrı inceleyeceğiz.
3.7.8.2. Form
[app-17.html] dosyasını [app-18.html] olarak kopyalayıp kodu şu şekilde değiştiriyoruz:
<div class="container">
<h1>Rdvmedecins - v1</h1>
<!-- bekleme mesajı -->
<div class="alert alert-warning" ng-show="waiting.visible">
...
</div>
<!-- istek -->
<div class="alert alert-info" ng-hide="waiting.visible">
<div class="row" style="margin-bottom: 20px">
<div class="col-md-3">
<h2 translate="{{medecins.title}}"></h2>
<select data-style="btn-primary" class="selectpicker">
<option ng-repeat="medecin in medecins.data" value="{{medecin.id}}">
{{medecin.titre}} {{medecin.prenom}} {{medecin.nom}}
</option>
</select>
</div>
<div class="col-md-3">
<h2 translate="{{calendar.title}}"></h2>
<div style="display:inline-block; min-height:290px;">
<datepicker ng-model="calendar.jour" min-date="calendar.minDate" show-weeks="true"
class="well well-sm"></datepicker>
</div>
</div>
</div>
<button class="btn btn-primary" ng-click="execute()">{{agenda.title|translate}}</button>
</div>
<!-- hata listesi -->
<div class="alert alert-danger" ng-show="errors.show">
...
</div>
<!-- takvim -->
<div id="agenda" ng-show="agenda.show">
...
</div>
</div>
...
<script type="text/javascript" src="rdvmedecins-06.js"></script>
- 5-7. satırlar: bekleme mesajı değişmez;
- satır 12-19: [bootstrap select] türündeki doktor listesi;
- 20-26. satırlar: Daha önce sunduğumuz [ui-bootstrap] takvimi. Seçilen günün [calendar.jour] şablonuna (ng-model özniteliği) yerleştirildiğine dikkat edin;
- 28. satır: takvimi görüntüleyen düğme;
- satır 32-34: hata listesi değişmez;
- satır 37-39: daha sonra tanıtacağımız takvim;
- 42. satır: JS kodu, [rdvmedecins-05.js] dosyasının kopyalanmasıyla [rdvmedecins-06.js] dosyasına aktarılır;
3.7.8.3. C denetleyicisi
Uygulamadaki JS kodu şu şekilde olur:

Değişikliklerden yalnızca [utils] hizmeti ve [rdvMedecinsCtrl] denetleyicisi etkilenecektir.
[rdvMedecinsCtrl] denetleyicisi şu şekilde değişir:
// denetleyici
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao', '$translate', '$timeout', '$filter', '$locale',
function ($scope, utils, config, dao, $translate, $timeout, $filter, $locale) {
// ------------------- şablon başlatma
// şablon
$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time: 3000};
$scope.server = {url: 'http://localhost:8080', kullanıcı adı: 'admin', şifre: 'admin'};
$scope.errors = {show: false, model: {}};
$scope.medecins = {
data: [
{id: 1, version: 1, titre: "Mme", nom: "PELISSIER", prenom: "Marie"},
{id: 2, version: 1, titre: "Mr", nom: "BROMARD", prenom: "Jacques"},
{id: 3, version: 1, titre: "Mr", nom: "JANDOT", prenom: "Philippe"},
{id: 4, version: 1, titre: "Melle", nom: "JACQUEMOT", prenom: "Justine"}
],
title: config.listMedecins};
$scope.agenda = {title: config.getAgendaTitle, data: undefined, show: false};
$scope.calendar = {title: config.getCalendarTitle, minDate: new Date(), jour: new Date()};
// açılır listeyi biçimlendiriyoruz
$timeout(function () {
$('.selectpicker').selectpicker();
});
// takvim için Fransızca yerel ayarları
angular.copy(config.locales['fr'], $locale);
...
}
])
;
- 7. satır: HTTP çağrısı yapılmadan önce 3 saniyelik bir bekleme süresi belirlenir;
- 8. satır: HTTP bağlantısı için gerekli öğeler sabit olarak belirlenir;
- 10-17. satırlar: doktor listesi sabit olarak tanımlanır;
- satır 18: [agenda] şablonu, görünümde takvimin görüntülenmesini yapılandırır;
- 19. satır: [calendar] şablonu, görünümde takvimin görüntülenmesini yapılandırır. [minDate] ile minimum tarih bugüne ve gün tarihi de bugüne sabitlenir;
- 21-23. satırlar: açılır liste, daha önce görülen yöntemle biçimlendirilir;
- 25. satır: Uygulamanın yerel ayarını 'fr' olarak ayarlıyoruz. Varsayılan olarak 'en' olarak ayarlanmıştır;
Takvim isteği sırasında yürütülen yöntem şöyledir:
// eylemi yürütme
$scope.execute = function () {
// form bilgileri
var idMedecin = $('.selectpicker').selectpicker('val');
// doğrulama
utils.debug("[homeCtrl] idMedecin", idMedecin);
utils.debug("[homeCtrl] jour", $scope.calendar.jour);
// tarihi yyyy-AA-gg formatına dönüştürme
var formattedJour = $filter('date')($scope.calendar.jour, 'yyyy-MM-dd');
// görünümün güncellenmesi
$scope.waiting.visible = true;
$scope.errors.show = false;
$scope.agenda.show = false;
...
};
- 4. satır: Seçilen doktorun [value] özniteliği alınır. Burada yine [bootstrap-select.min.js] dosyasından gelen [selectpicker] yöntemi kullanılır. Açılır menüdeki seçeneklerin biçimini hatırlamak gerekir:
<option ng-repeat="medecin in medecins.data" value="{{medecin.id}}">
{{medecin.titre}} {{medecin.prenom}} {{medecin.nom}}
Dolayısıyla seçeneğin değeri (value özniteliği), doktorun [id] kimlik numarasıdır.
- 11. satır: Kullanıcı tarafından seçilen gün, web sunucusunun beklediği tarih biçimi olan [aaaa-mm-jj] biçimine dönüştürülür;
- 13-15. satırlar: [execute] yöntemi tamamlandığında, bekleme çubuğu görüntülenecek ve geri kalan her şey gizlenecektir;
Kod şu şekilde devam eder:
// simüle edilmiş bekleme
var task = utils.waitForSomeTime($scope.waiting.time);
// doktorun randevu takvimi isteniyor
var promise = task.promise.then(function () {
// hizmet URL'in yolu
var path = config.urlSvrAgenda + "/" + idMedecin + "/" + formattedJour;
// randevu takvimi isteniyor
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password, path);
// görev tamamlama taahhüdü gönderiliyor
return task.promise;
});
// [dao] hizmet çağrısının sonucu analiz ediliyor
promise.then(function (result) {
// bekleme sona erdi
$scope.waiting.visible = false;
// hata mı?
if (result.err == 0) {
// takvim şablonu hazırlanıyor
$scope.agenda.data = result.data;
$scope.agenda.show = true;
// zaman çizelgesinin görüntülenişi biçimlendiriliyor
angular.forEach($scope.agenda.data.creneauxMedecin, function (creneauMedecin) {
creneauMedecin.creneau.text = utils.getTextForCreneau(creneauMedecin.creneau);
});
// görünüm görüntülendikten sonra tabloyu biçimlendirmek için bir olay oluşturuluyor
$timeout(function () {
$("#creneaux").footable();
});
} else {
// takvimi alırken hatalar oluştu
$scope.errors = {
title: config.getAgendaErrors,
messages: utils.getErrors(result),
show: true
};
}
- 2. satır: 3 saniyelik asenkron bekleme görevi;
- satır 5-10: bu bekleme süresi sona erdiğinde yürütülecek kod;
- 6. satır: sorgulanan URL, [/getAgendaMedecinJour/1/2014-06-25] oluşturulur;
- 8. satır: URL sorgulanır. Asenkron bir görev başlar;
- satır 10: bu asenkron görevin sözü yerine getirilir;
- satır 14-38: HTTP çağrısı yanıtını döndürdüğünde yürütülecek kod;
- 13. satır: [result], [dao.getData] yöntemi tarafından gönderilen yanıttır. Burada web sunucusunun yanıt biçimini hatırlamak gerekir:
![]() |
- satırdaki [result.data] parametresi, yukarıdaki [data] [1] özniteliğidir. Bu öznitelik ise yukarıdaki [creneauxMedecin] [2] özniteliğini içerir. Bu öznitelik, her biri için aşağıdaki iki bilgiyi içeren bir zaman aralığı tablosudur:
- [rv]: Bir randevunun JSON biçimi veya bu zaman diliminde alınmış bir randevu yoksa [null];
- [hDeb, mDeb, hFin, mFin]: zaman aralığına ait saat bilgileri;
Kontrolör koduna geri dönelim:
- 15. satır: bekleme sona erdi;
- 19. satır: takvimin görüntülenmesini kontrol eden [$scope.agenda] şablonu doldurulur;
- 20. satır: takvim görünür hale getirilir;
- 22-24. satırlar: az önce bahsettiğimiz [creneauxMedecin] tablosundaki her bir C öğesini tek tek gözden geçiriyoruz;
- 23. satır: Her bir C öğesinin, zaman aralığını belirten [creneau] özniteliği vardır. Bu özniteliğe, zaman aralığının [10h20:10h40] biçiminde metin olarak gösterilmesini sağlayan [text] özniteliği eklenir;
- 26-28. satırlar: Takvimdeki zaman aralıklarını görüntülemek için kullanılan HTML tablosunu "duyarlı" hale getiriyoruz. Bu kavramı 3.6.7. paragrafta ele almıştık;
![]() |
- 27. satır: Tabloyu 'duyarlı' hale getirmek için [footable] yöntemini uygulamak gerekir. Burada, [bootstrap-select] bileşeninde karşılaştığımızla aynı zorlukla karşılaşırız. 17. satırı olduğu gibi yazarsak, tablonun 'duyarlı' olmadığını görürüz. Bu sorunu, [$timeout] işleviyle (satır 26) aynı şekilde çözebiliriz;
- 31-34. satırlar: HTTP çağrısının başarısız olduğu durum. Bu durumda hata mesajları görüntülenir;
3.7.8.4. Takvimin görüntülenmesi
Şimdi [app-18.html] dosyasındaki takvim koduna geri dönüyoruz. Kod şu şekildedir:
<!-- takvim -->
<div id="agenda" ng-show="agenda.show">
<!-- Muayene zaman aralığı olmayan doktor durumu -->
<h4 class="alert alert-danger" ng-if="agenda.data.creneauxMedecin.length==0"
translate="agenda_medecinsanscreneaux"></h4>
<!-- doktorun randevu takvimi -->
<div class="row tab-content alert alert-warning" ng-if="agenda.data.creneauxMedecin.length!=0">
<div class="tab-pane active col-md-6">
<table creneaux-table id="creneaux" class="table">
<thead>
<tr>
<th data-toggle="true">
<span translate="agenda_creneauhoraire"></span>
</th>
<th>
<span translate="agenda_client">Client</span>
</th>
<th data-hide="phone">
<span translate="agenda_action">Action</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="creneauMedecin in agenda.data.creneauxMedecin">
<td>
<span
ng-class="! creneauMedecin.rv ? 'status-metro status-active' : 'status-metro status-suspended'">
{{creneauMedecin.creneau.text}}
</span>
</td>
<td>
<span>{{creneauMedecin.rv.client.titre}} {{creneauMedecin.rv.client.prenom}} {{creneauMedecin.rv.client.nom}}</span>
</td>
<td>
<a href="" ng-if="!creneauMedecin.rv" translate="agenda_reserver" class="status-metro status-active">
</a>
<a href="" ng-if="creneauMedecin.rv" translate="agenda_supprimer" class="status-metro status-suspended">
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
- 4-5. satırlar: [agenda.data]'in takvim, [agenda.data.creneauxMedecin]'in ise [creneauMedecin] türünde nesnelerden oluşan bir dizi olduğunu hatırlayalım. Bu son türdeki her öğenin, bir zaman aralığı olan [creneauMedecin.creneau] özniteliği vardır. Her zaman aralığında bizi ilgilendiren iki öğe bulunur:
- [creneauMedecin.creneau.rv], bu zaman aralığında yer alan olası RV (rv!=null) öğesidir;
- [creneauMedecin.creneau.text], zaman aralığının [début:fin] metnidir;
- 4. satır: Doktorun randevu aralığı yoksa özel bir mesaj görüntüler. Bu olasılık düşük olsa da, veritabanımızın eksik olması nedeniyle bu durum söz konusu olabilir. Mesajın HTML olarak oluşturulup oluşturulmayacağı, [ng-if] yönergesi tarafından kontrol edilir;

[ng-if] yönergesi, [ng-show, ng-hide] yönergelerinden farklıdır. Sonuncular, belgede bulunan bir alanı yalnızca gizlemekle yetinir. [ng-if='false'] ise, alan belgeden kaldırılır. Burada örnek olarak kullanıldı;
- 9. satır: [id='creneaux'] özniteliği önemlidir. Aşağıdaki komutta kullanılan da budur:
$("#creneaux").footable();
- 10-22. satırlar: [1] tablosunun başlıklarını görüntüler;
- 23-45. satırlar: [2] tablosunun içeriğini görüntüler;
![]() |
- 24. satır: [agenda.data.creneauxMedecin] tablosu taranır;
- 26-29. satırlar: [3] metnini yazdırır. Elemanın [class] özniteliğini oluşturacak olan [ng-class] yönergesi kullanılır. Burada, [creneauMedecin.rv==null] varsa, bu zaman aralığının boş olduğu anlamına gelir ve metne yeşil arka plan rengi verilir. Aksi takdirde, kırmızı arka plan rengi verilir;
- 32. satır: RV veya [4]'in alındığı müşterinin adı yazılır. [rv==null] ise bu bilgiler mevcut değildir, ancak Angular bu durumu doğru şekilde yönetir ve hata bildirmez;
- 34-39. satırlar: [Réserver] veya [Supprimer] düğmelerinden birini görüntüler. Randevunun var olup olmadığına göre bu düğmelerden biri seçilir;
3.7.8.5. Web sunucusunun değiştirilmesi
Önceki örneklerde olduğu gibi, URL ve [/getAgendaMedecinJour]'in CORS başlıklarını gönderebilmesi için web sunucusunun değiştirilmesi gerekir:
![]() |
[RdvMedecinsCorsController] sınıfına yeni bir yöntem eklenir:
// doktorun randevu takvimi
@RequestMapping(value = "/getAgendaMedecinJour/{idMedecin}/{jour}", method = RequestMethod.OPTIONS)
public void getAgendaMedecinJour(HttpServletResponse response) {
sendOptions(response);
}
Bu yöntem, HTTP ve [OPTIONS] istekleri için CORS başlıklarını gönderecektir. Aynı işlemi, [RdvMedecinsController] sınıfındaki HTTP ve [GET] istekleri için de yapmalıyız:
@RequestMapping(value = "/getAgendaMedecinJour/{idMedecin}/{jour}", method = RequestMethod.GET)
public Reponse getAgendaMedecinJour(@PathVariable("idMedecin") long idMedecin, @PathVariable("jour") String jour, HttpServletResponse response) {
// başlıklar CORS
rdvMedecinsCorsController.getAgendaMedecinJour(response);
...
}
3.7.8.6. Yönergelerin kullanımı
Daha önce yapıldığı gibi, DOM ile ilgili işlemleri yönergelere aktaracağız. DOM ile ilgili iki işlemimiz var:
- görünümün ilk görüntülenmesi sırasında:
// açılır listeye stil verilir
$timeout(function () {
$('.selectpicker').selectpicker();
});
- takvimin görüntülenmesi sırasında:
// görünüm görüntülendikten sonra tabloyu biçimlendirmek için bir olay oluşturulur
$timeout(function () {
$("#creneaux").footable();
});
İlk durum için, daha önce tanıtılan [selectEnable] yönergesini kullanacağız. İkinci durum için ise, aşağıdaki JS [footable.js] dosyasında [footable] yönergesini oluşturuyoruz:
angular.module("rdvmedecins").directive('footable', ['$timeout', 'utils', function ($timeout, utils) {
return {
link: function (scope, element, attrs) {
utils.debug("directive footable");
$timeout(function () {
$("#creneaux").footable();
})
}
}
}]);
Dolayısıyla, [selectEnable] yönergesinde olduğu gibi aynı tekniği kullanıyoruz.
HTML [app-18.html] kodu, [app-18B.html]'te kopyalanır. Ardından şu şekilde değiştirilir:
<select data-style="btn-primary" class="selectpicker" select-enable="">
<option ng-repeat="medecin in medecins.data" value="{{medecin.id}}">
{{medecin.titre}} {{medecin.prenom}} {{medecin.nom}}
</option>
</select>
- 1. satır: [selectEnable] yönergesi ([select-enable] özniteliği aracılığıyla) doktorların <select> etiketine uygulanır;
<div class="row tab-content alert alert-warning" ng-if="agenda.data.creneauxMedecin.length!=0">
<div class="tab-pane active col-md-6">
<table id="creneaux" class="table" footable="">
<thead>
<tr>
- 3. satır: [footable] yönergesi ([footable] özniteliği aracılığıyla) takvimin HTML tablosuna uygulanır;
<script type="text/javascript" src="rdvmedecins-06B.js"></script>
<!-- yönergeler -->
<script type="text/javascript" src="selectEnable.js"></script>
<script type="text/javascript" src="footable.js"></script>
- 3-4. satırlar: Her iki yönergenin JS dosyalarına atıfta bulunulur;
- 1. satır: JS dosyasındaki [app-18B.html] kodu, [rdvmedecins-06B.js] dosyasında yinelenen [app-18.html] dosyasındaki JS kodudur;
[rdvmedecins-06B.js] dosyası, iki ayrıntı dışında [rdvmedecins-06.js] dosyasıyla aynıdır. DOM ile ilgili satırlar kaybolur:
// açılır listeye stil veriliyor
$timeout(function () {
$('.selectpicker').selectpicker();
});
// görünüm görüntülendikten sonra tabloya stil uygulamak için bir olay oluşturulur
$timeout(function () {
$("#creneaux").footable();
});
Böylece, [app-18B.html] uygulamasının çalıştırılması, [app-18.html] uygulamasının çalıştırılmasıyla aynı sonuçları verir.
3.7.9. Örnek 9: Rezervasyon oluşturma ve iptal etme
Şimdi, rezervasyon oluşturmaya ve iptal etmeye olanak tanıyan bir uygulamayı sunuyoruz.
3.7.9.1. Uygulamanın V görünümü
Aşağıdaki formu tanıtacağız:
![]() |
- [1]'te rezervasyon yapılabilir. Yapılacak rezervasyon rastgele bir müşteri için olacaktır;
- [2] sayfasında, yaptığımız rezervasyonları silebileceğiz;
[app-18.html] dosyasını [app-19.html] olarak kopyalayıp kodu şu şekilde değiştireceğiz:
<div class="container">
<h1>Rdvmedecins - v1</h1>
<!-- bekleme mesajı -->
<div class="alert alert-warning" ng-show="waiting.visible">
...
</div>
<!-- hata listesi -->
<div class="alert alert-danger" ng-show="errors.show">
...
</div>
<!-- randevu takvimi -->
<div id="agenda" ng-show="agenda.show">
..
<!-- doktorun randevu takvimi -->
<div class="row tab-content alert alert-warning" ng-if="agenda.data.creneauxMedecin.length!=0">
<div class="tab-pane active col-md-6">
<table id="creneaux" class="table" footable="">
...
<tbody>
<tr ng-repeat="creneauMedecin in agenda.data.creneauxMedecin">
...
<td>
<a href="" ng-if="!creneauMedecin.rv" translate="agenda_reserver" class="status-metro status-active" ng-click="reserver(creneauMedecin.creneau.id)">
</a>
<a href="" ng-if="creneauMedecin.rv" translate="agenda_supprimer" class="status-metro status-suspended" ng-click="supprimer(creneauMedecin.rv.id)">
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
....
<script type="text/javascript" src="rdvmedecins-07.js"></script>
<script type="text/javascript" src="footable.js"></script>
- 5-7. satırlar: Bekleme mesajı önceki sürümünkiyle aynıdır;
- 10-12. satırlar: hata mesajı önceki sürümünkiyle aynıdır;
- 15-36. satırlar: Takvim, iki ayrıntı dışında önceki sürümünkiyle aynıdır:
- 26. satır: [réserver] düğmesine tıklama (ng-click özniteliği), V görünümünün M modelindeki [reserver] yöntemi tarafından yönetilir. Bu yönteme rezervasyon zaman aralığının numarası aktarılır;
- 26. satır: [supprimer] düğmesine tıklama, V görünümünün M modelindeki [reserver] yöntemi tarafından yönetilir. Bu yönteme silinecek randevunun numarası aktarılır;
- satır 39: Uygulamayı yöneten JS kodu, [rdvmedecins-07.js] dosyasında bulunur;
- 40. satır: 20. satırda uygulanan [footable] yönergesine ait JS kodu;
3.7.9.2. C denetleyicisi
[rdvmedecins-07.js] dosyasındaki JS kodu, önce [rdvmedecins-06.js] dosyasından kopyalanarak elde edilir. Ardından değiştirilir. Her zamanki gibi büyük kod blokları mevcuttur. Değişiklikler esas olarak denetleyicide yapılır:

V görünümünün C denetleyicisini birkaç adımda açıklayacağız.
3.7.9.3. C denetleyicisinin başlatılması
Denetleyicinin başlatma kodu şöyledir:
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao', '$translate', '$timeout', '$filter', '$locale',
function ($scope, utils, config, dao, $translate, $timeout, $filter, $locale) {
// ------------------- şablon başlatma
// şablon
$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time: 3000};
$scope.server = {url: 'http://localhost:8080', kullanıcı adı: 'admin', şifre: 'admin'};
$scope.errors = {show: false, model: {}};
$scope.medecins = {
data: [
{id: 1, version: 1, titre: "Mme", nom: "PELISSIER", prenom: "Marie"},
{id: 2, version: 1, titre: "Mr", nom: "BROMARD", prenom: "Jacques"},
{id: 3, version: 1, titre: "Mr", nom: "JANDOT", prenom: "Philippe"},
{id: 4, version: 1, titre: "Melle", nom: "JACQUEMOT", prenom: "Justine"}
],
title: config.listMedecins
};
var médecin = $scope.medecins.data[0];
var clients = [
{id: 1, version: 1, titre: "Mr", nom: "MARTIN", prenom: "Jules"},
{id: 2, version: 1, titre: "Mme", nom: "GERMAN", prenom: "Christine"},
{id: 3, version: 1, titre: "Mr", nom: "JACQUARD", prenom: "Maurice"},
{id: 4, version: 1, titre: "Melle", nom: "BISTROU", prenom: "Brigitte"}
];
// tarih için Fransızca yerel ayar
angular.copy(config.locales['fr'], $locale);
var today = new Date();
var formattedDay = $filter('date')(today, 'yyyy-MM-dd');
var fullDay = $filter('date')(today, 'fullDate');
$scope.agenda = {title: config.agendaTitle, data: undefined, show: false, model: {titre: médecin.titre, prenom: médecin.prenom, nom: médecin.nom, jour: fullDay}};
// ---------------------------------------------------------------- başlangıç takvimi
// genel asenkron görev
var task;
// takvim isteniyor
getAgenda();
// ------------------------------------------------------------------ rezervasyon
$scope.reserver = function (creneauId) {
....
};
// ------------------------------------------------------------ silme RV
$scope.supprimer = function (idRv) {
...
};
// takvimin alınması
function getAgenda() {
...
}
// iptal beklemede
function cancel() {
...
}
} ]);
- 6. satır: bekleme mesajının yapılandırılması. Varsayılan olarak, HTTP çağrısı yapılmadan önce 3 saniye beklenecektir;
- 7. satır: HTTP çağrıları için gerekli bilgiler;
- 8. satır: hata mesajının yapılandırılması;
- satır 9-17: sabit doktorlar;
- 18. satır: özel bir doktor. Randevular, bu doktorun muayene saatleri için alınacaktır;
- 19-24. satırlar: sabit müşteriler;
- 26. satır: Fransız tarihlerini işlemek istiyoruz;
- satır 27: randevular bugünün tarihine göre alınacak;
- satır 28: web üzerinden randevu hizmeti, tarihleri 'yyyy-aa-gg' biçiminde bekliyor;
- satır 29: bugünün tarihi [jeudi 26 juin 2014] biçiminde;
- satır 30: takvimin yapılandırılması. [model] özniteliği, görüntülenecek uluslararasılaştırılmış mesajın parametrelerini taşır:
agenda_title: "Agenda de {{titre}} {{prenom}} {{nom}} le {{jour}}"
- satır 35: [task] küresel değişkeni, belirli bir anda yürütülmekte olan asenkron görevi temsil eder;
- satır 37: başlangıç takvimi istenir;
Sayfanın ilk yüklenmesi sırasında yapılan işlemler bunlardır. Her şey yolunda giderse, görünüm Bayan PELISSIER'in o günkü ajandasını görüntüler.

3.7.9.4. Takvimin Alınması
Takvim, aşağıdaki [getAgenda] yöntemi ile alınır:
// takvimin alınması
function getAgenda() {
// URL hizmet yolu
var path = config.urlSvrAgenda + "/" + médecin.id + "/" + formattedDay;
// takvim isteniyor
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password, path);
// bekleme mesajı
$scope.waiting.visible = true;
// [dao] hizmet çağrısının sonucu analiz ediliyor
task.promise.then(function (result) {
// bekleme sona erdi
$scope.waiting.visible = false;
// hata mı?
if (result.err == 0) {
// takvim şablonu hazırlanıyor
$scope.agenda.data = result.data;
$scope.agenda.show = true;
// zaman çizelgesinin görüntülenmesi biçimlendiriliyor
angular.forEach($scope.agenda.data.creneauxMedecin, function (creneauMedecin) {
creneauMedecin.creneau.text = utils.getTextForCreneau(creneauMedecin.creneau);
});
} else {
// takvimi alırken hatalar oluştu
$scope.errors = {title: config.getAgendaErrors, messages: utils.getErrors(result), show: true};
}
});
}
Bu kod, önceki uygulamada incelenen koddur. İki değişiklik vardır:
- HTTP çağrısından önce simüle edilmiş bir bekleme yoktur;
- 4. satır: denetleyicinin başlatılması sırasında oluşturulan doktor ve biçimlendirilmiş gün kullanılır;
Bu kod, [reserver] ve [supprimer] fonksiyonları tarafından da kullanıldığı için ayrı bir fonksiyon içinde yer almaktadır.
3.7.9.5. Zaman aralığı rezervasyonu
![]() | ![]() |
Müşterilerin rastgele seçildiğini hatırlatırız.
Rezervasyon kodu şöyledir:
$scope.reserver = function (creneauId) {
utils.debug("réservation du créneau", creneauId);
// [id] ile tanımlanan zaman aralığında rastgele bir müşteri ile RV oluşturuluyor
var idClient = clients[Math.floor(Math.random() * clients.length)].id;
utils.debug("réservation du créneau pour le client", idClient);
// simüle edilmiş bekleme
$scope.waiting.visible = true;
var task = utils.waitForSomeTime($scope.waiting.time);
// zaman aralığı ekleniyor
var promise = task.promise.then(function () {
// hizmetin URL yolunu
var path = config.urlSvrResaAdd;
// hizmete iletilecek veriler
var post = {jour: formattedDay, idCreneau: creneauId, idClient: idClient};
// asenkron görev başlatılıyor
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password, path, post);
// görevin tamamlanma taahhüdü döndürülür
return task.promise;
});
// görev sonucunun analizi
promise = promise.then(function (result) {
if (result.err != 0) {
// rv'yi doğrulamada hatalar oluştu
$scope.errors = {title: config.postResaErrors, messages: utils.getErrors(result, $filter), show: true};
} else {
// yeni ajanda isteniyor
getAgenda();
}
});
};
- 1. satır: [reserver] işlevinin parametresinin zaman aralığı numarası (id özniteliği) olduğu hatırlatılmaktadır;
- 4. satır: Başlatma kodunda sabit olarak tanımlanan müşteri listesinden rastgele bir müşteri seçilir. Bu müşterinin [id] kimlik numarası alınır;
- 7-8. satırlar: 3 saniyelik bekleme süresi;
- satır 11-18: bu satırlar ancak 3 saniye dolduktan sonra yürütülür;
- 12. satır: [/ajouterRv] rezervasyon hizmetine ait URL. Bu URL, şimdiye kadar karşılaştıklarımızdan farklıdır. Web hizmetinde şu şekilde tanımlanmıştır:
@RequestMapping(value = "/ajouterRv", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Reponse ajouterRv(@RequestBody PostAjouterRv post, HttpServletResponse response) {
- (devamı)
- 1. satır: URL'in parametresi yoktur ve bir POST ile çağrılır;
- 2. satır: Gönderilen parametreler, bir JSON nesnesi biçimindedir. Bu nesne, [post] (@RequestBody) parametresinde serileştirilecektir;
POST'in bir örneğini daha önce görmüştük (2.12.2. paragraf):
![]() |
- [0]'te, web hizmetinin URL'i;
- [1]'e dönüştürüldü; burada POST yöntemi kullanıldı;
- [2]'te, web hizmetine {gün, idClient, idCreneau} biçiminde iletilen bilgilerin metni JSON;
- [3]'te, istemci web hizmetine JSON bilgilerini gönderdiğini belirtir;
[reserver] işlevindeki JS koduna geri dönelim:
- 14. satır: Gönderilecek değer, JS nesnesi biçiminde oluşturulur. Angular, gönderim sırasında bunu JSON olarak serileştirecektir;
- 16. satır: HTTP işlevi çağrılır. Gönderilecek değer, [dao.getData] işlevinin son parametresidir. Bu parametre mevcut olduğunda, [dao.getData] işlevi, GET yerine POST oluşturur (3.7.6.4 numaralı paragrafta yer alan koda bakınız);
- 18. satır: HTTP çağrısının sözü döndürülür;
- 23-29. satırlar: Yalnızca HTTP çağrısı yanıtını verdiğinde yürütülür;
- 23. satır: [result] parametresi, [err,data] veya [err,messages] biçimindedir; burada [err] bir hata kodudur;
- 23-26. satırlar: Hata oluşmuşsa, hata mesajı görüntülenir;
- 28. satır: Rezervasyon başarıyla tamamlandıysa, yeni takvim yeniden görüntülenir;
3.7.9.6. Sunucu değişikliği
![]() |
[RdvMedecinsCorsController] sınıfına aşağıdaki yöntemi ekliyoruz:
// seçenekler müşteriye gönderiliyor
private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// başlık belirleniyor CORS
response.addHeader("Access-Control-Allow-Origin", "*");
// başlık onaylandı [authorization]
response.addHeader("Access-Control-Allow-Headers", "authorization");
}
@RequestMapping(value = "/ajouterRv", method = RequestMethod.OPTIONS)
public void ajouterRv(HttpServletResponse response) {
sendOptions(response);
}
Ekleme, 10-13. satırlarda yapılır. 2-8. satırlardaki başlıklar, URL [/ajouterRv] (10. satır) ve HTTP [OPTIONS] (10. satır) yöntemlerine gönderilecektir.
[RdvMedecinsController] sınıfı şu şekilde değiştirilir:
@RequestMapping(value = "/ajouterRv", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Reponse ajouterRv(@RequestBody PostAjouterRv post, HttpServletResponse response) {
// CORS başlıkları
rdvMedecinsCorsController.ajouterRv(response);
...
[POST] yöntemi (satır 1) ve URL ile [/ajouterRv] (satır 1) için, az önce [RdvMedecinsCorsController]'e eklediğimiz yöntem çağrılır (satır 4) ve böylece HTTP ve [OPTIONS] yöntemlerinde olduğu gibi aynı HTTP başlıklarını döndürür.
3.7.9.7. Tests
İlk testimizde rastgele bir zaman aralığı ayıralım:
![]() |
Bu tür durumlarda her zaman olduğu gibi, konsol günlüklerine bakmak gerekir:
[dao] getData[/ajouterRv] error réponse : {"data":"","status":0,"config":{"method":"POST","transformRequest":[null],"transformResponse":[null],"timeout":1000,"url":"http://localhost:8080/ajouterRv","data":{"jour":"2014-06-30","idCreneau":1,"idClient":4},"headers":{"Accept":"application/json, text/plain, */*","Authorization":"Basic YWRtaW46YWRtaW4=","Content-Type":"application/json;charset=utf-8"}},"statusText":""}
[dao.getData] yöntemi, [status=0] ile başarısız oldu; bu, isteği Angular'ın iptal ettiği anlamına gelir. Hatanın nedeni günlüklerde belirtilmiştir:
XMLHttpRequest cannot load http://localhost:8080/ajouterRv. Request header field Content-Type is not allowed by Access-Control-Allow-Headers.
Ağ trafiğine bakıldığında şu durum gözlemleniyor:
![]() |
- [1] ve [2]'te: yalnızca HTTP isteği vardı, [OPTIONS] isteği;
- [3]'te, Angular istemcisi iki yetki talep eder:
- HTTP ve [accept, authorization, content-type] başlıklarını gönderme yetkisi;
- POST komutunu gönderme yetkisi;
- [4]'te: sunucu, [authorization] başlığını yetkilendirir. Sunucu tarafında bu yetkilendirmeyi bizim gönderdiğimizi hatırlayalım;
Dolayısıyla yenilik şudur: POST işleminde, Angular istemcisi sunucudan daha fazla yetki talep eder. Bu nedenle, sunucunun bu yetkileri vermesi için aşağıdaki değişikliği yapmamız gerekir:
![]() |
[RdvMedecinsCorsController] sınıfında, OPTIONS, GET ve POST komutları için gönderilen HTTP başlıklarını oluşturan özel yöntemi değiştiriyoruz:
// seçeneklerin istemciye gönderilmesi
private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// CORS başlığı ayarlanıyor
response.addHeader("Access-Control-Allow-Origin", "*");
// belirli başlıklara izin veriliyor
response.addHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
// POST'e izin veriliyor
response.addHeader("Access-Control-Allow-Methods", "POST");
}
}
- 7. satır: HTTP ve [accept, content-type] başlıkları için bir yetki eklendi;
- 9. satır: POST yöntemi için bir yetki eklendi;
Sunucuyu yeniden başlattıktan sonra testi tekrar yapıyoruz:
![]() |
Bu sefer rezervasyon yapılabildi.
3.7.9.8. Randevuyu silme
![]() | ![]() |
[supprimer] işlevinin kodu şöyledir:
$scope.supprimer = function (idRv) {
utils.debug("suppression rv n°", idRv);
// simüle edilmiş bekleme
$scope.waiting.visible = true;
task = utils.waitForSomeTime($scope.waiting.time);
// zaman aralığı eklenir
var promise = task.promise.then(function () {
// hizmet yolunun URL
var path = config.urlSvrResaRemove;
// hizmete iletilecek veriler
var post = {idRv: idRv};
// asenkron görev başlatılıyor
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password, path, post);
// görevin tamamlanma taahhüdü döndürülür
return task.promise;
});
// görev sonucunun analizi
promise = promise.then(function (result) {
if (result.err != 0) {
// rv'yi silme işleminde hatalar oluştu
$scope.errors = {title: config.postRemoveErrors, messages: utils.getErrors(result, $filter), show: true};
// UI güncelleniyor
$scope.waiting.visible = false;
} else {
// yeni ajanda isteniyor
getAgenda();
}
});
};
- 1. satır: İşlevin parametresinin, silinecek randevunun numarası olduğunu unutmamak gerekir. Burada, rezervasyon koduna çok benzer bir kod bulunmaktadır. Yalnızca farklılıkları açıklayacağız;
- 9. satır: Hizmetin URL kodu burada [/supprimerRV] olarak geçmektedir ve buraya da bir POST aracılığıyla erişilmektedir:
@RequestMapping(value = "/supprimerRv", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Reponse supprimerRv(@RequestBody PostSupprimerRv post, HttpServletResponse response) {
Gönderilen parametre burada da JSON biçiminde iletilmiştir. 2.12.17 numaralı paragrafta, elle oluşturulan POST'in yapısını göstermiştik:
![]() |
- [1]'te, web hizmetinin URL'i;
- [2]'e dönüştürülürken, POST yöntemi kullanılır;
- [3]'te, web hizmetine {idRv} biçiminde iletilen bilgilerin metni JSON;
- [4]'te, istemci web hizmetine JSON bilgilerini gönderdiğini belirtir;
[supprimer] işlevinin JS koduna geri dönelim:
- 11. satır: post nesnesi oluşturulur. Angular bunu otomatik olarak JSON formatında serileştirecektir;
Kodun geri kalanı, rezervasyon koduna benzerdir.
3.7.9.9. Sunucu tarafında yapılan değişiklikler
Sunucu tarafında şu değişiklikleri yapıyoruz:
![]() |
[RdvMedecinsCorsController] sınıfına aşağıdaki yöntemi ekliyoruz:
// seçenekler müşteriye gönderiliyor
private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// başlık düzeltiliyor CORS
response.addHeader("Access-Control-Allow-Origin", "*");
// belirli başlıklar onaylanıyor
response.addHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
// POST'e izin veriliyor
response.addHeader("Access-Control-Allow-Methods", "POST");
}
}
...
@RequestMapping(value = "/supprimerRv", method = RequestMethod.OPTIONS)
public void supprimerRv(HttpServletResponse response) {
sendOptions(response);
}
Ekleme, 13-16. satırlara yapılmıştır. 2-10. satırlardaki başlıklar, URL [/supprimerRv] (13. satır) ve HTTP [OPTIONS] (13. satır) yöntemlerine gönderilecektir.
[RdvMedecinsController] sınıfı ise şu şekilde değiştirilmiştir:
@RequestMapping(value = "/supprimerRv", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Reponse supprimerRv(@RequestBody PostSupprimerRv post, HttpServletResponse response) {
// CORS başlıkları
rdvMedecinsCorsController.supprimerRv(response);
...
[POST] yöntemi (satır 1) ve URL ile [/supprimerRv] (satır 1) için, az önce [RdvMedecinsCorsController]'e eklediğimiz yöntem çağrılır (satır 4) ve böylece HTTP yöntemiyle aynı başlıkları döndürür; bu durum HTTP ve [OPTIONS] yöntemleri için de geçerlidir.
3.7.10. Örnek 10: Rezervasyon oluşturma ve iptal etme - 2
Şimdi, daha önce sunduğumuz uygulamanın aynısını sunuyoruz; ancak rastgele bir müşteri için rezervasyon yapmak yerine, müşteri bir açılır listeden seçilecek.
3.7.10.1. Uygulamanın V görünümü
Aşağıdaki formu sunacağız:
![]() |
Müşteriler, [1]'ten seçilecektir.
Kod, önceki uygulamadakine benzer olduğundan, sadece temel farklılıkları ele alacağız.
[app-19.html] dosyasını [app-20.html] olarak kopyalayıp, ardından [1] adlı müşteri açılır listesi kodunu oluşturuyoruz:
<!-- müşteri listesi -->
<div class="alert alert-info">
<h3>{{agenda.title|translate:agenda.model}}</h3>
<div class="row" ng-show="clients.show">
<div class="col-md-3">
<h2 translate="{{clients.title}}"></h2>
<select data-style="btn-primary" class="selectpicker" select-enable="" ng-if="clients.data">
<option ng-repeat="client in clients.data" value="{{client.id}}">
{{client.titre}} {{client.prenom}} {{client.nom}}
</option>
</select>
</div>
</div>
</div>
- 8-12. satırlar: açılır liste, [bootstrap-select] bileşeni ile uygulanacaktır;
- 1. satır: [selectEnable] yönergesi, [select-enable] özniteliği aracılığıyla uygulanır;
- satır 1: <select> etiketi, yalnızca [clients.data] varsa oluşturulur (# null, undefined). Bu nokta önemlidir ve 3.7.7.8 numaralı paragrafta açıklanmıştır;
Ayrıca, yeni JS dosyalarını içe aktarıyoruz:
<script type="text/javascript" src="rdvmedecins-08.js"></script>
<!-- yönergeler -->
<script type="text/javascript" src="selectEnable.js"></script>
<script type="text/javascript" src="footable.js"></script>
- 1. satır: [rdvmedecins-08.js] dosyası, [rdvmedecins-0.js] dosyasının kopyalanmasıyla elde edilir;
- 3-4. satırlar: her iki yönergenin dosyaları içe aktarılır;
3.7.10.2. C denetleyicisi
C denetleyicisinin kodu şu şekilde değişir:
// denetleyici
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao', '$translate', '$timeout', '$filter', '$locale',
function ($scope, utils, config, dao, $translate, $timeout, $filter, $locale) {
// ------------------- şablon başlatma
...
// müşteriler
$scope.clients = {title: config.listClients, show: false, model: {}};
//------------------------------------------- görünüm başlatma
// genel asenkron görev
var task;
// önce müşteriler, ardından takvim isteniyor
getClients().then(function () {
getAgenda();
});
...
// eylemin yürütülmesi
function getClients() {
....
};
} ]);
- 8. satır: [$scope.clients] nesnesi, V görünümündeki müşteri açılır menüsünü yapılandırır;
- 14-16. satırlar: Asenkron bir şekilde, önce müşteri listesi istenir; bu liste alındıktan sonra ise Bayan PELISSIER’in bugünkü randevu takvimi istenir. Burada kullanılan sözdizimi, yalnızca [getClients] işlevinin bir söz (promise) döndürmesi nedeniyle çalışmaktadır;
[getClients] yöntemi müşteri listesini alır:
function getClients() {
// UI güncelleniyor
$scope.waiting.visible = true;
$scope.clients.show = false;
$scope.errors.show = false;
// müşteri listesi isteniyor;
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password, config.urlSvrClients);
var promise = task.promise;
// önceki çağrının sonucunu analiz ediliyor
promise = promise.then(function (result) {
// sonuç={hata: 0, veriler: [client1, client2, ...]}
// sonuç={hata: n, mesajlar: [msg1, msg2, ...]}
if (result.err == 0) {
// toplanan veriler modele eklenir
$scope.clients.data = result.data;
// UI güncellenir
$scope.clients.show = true;
$scope.waiting.visible = false;
} else {
// müşteri listesini alırken hatalar oluştu
$scope.errors = { title: config.getClientsErrors, messages: utils.getErrors(result), show: true, model: {}};
// UI dosyası güncelleniyor
$scope.waiting.visible = false;
}
});
// taahhüt veriliyor
return promise;
};
Bu, daha önce karşılaştığımız ve üzerinde yorum yaptığımız bir koddur. Dikkat edilmesi gereken önemli nokta 31. satırdır:
- 27. satır: 10. satırdaki promise, yani kodda alınan son promise döndürülür. Bu promise, ancak HTTP çağrısı yanıtını verdiğinde elde edilecektir;
[reserver] yöntemi biraz değişiyor:
$scope.reserver = function (creneauId) {
utils.debug("réservation du créneau", creneauId);
// seçilen müşteri için bir RV oluşturuluyor
var idClient = $(".selectpicker").selectpicker('val');
...
});
- 4. satır: Artık rastgele bir müşteri için değil, müşteri listesinden seçilen müşteri için yer ayrılıyor.
3.7.11. Örnek 11: bir [selectEnable2] yönergesi
Bu örnek, yönergelere geri dönüyor.
3.7.11.1. V görünümü
Uygulama aşağıdaki görünümü görüntüler:
![]() |
3.7.11.2. Görünümün HTML kodu
[app-21.html] görünümünün HTML kodu şöyledir:
<div class="container">
<h1>Rdvmedecins - v1</h1>
<!-- bekleme mesajı -->
<div class="alert alert-warning" ng-show="waiting.visible">
...
</div>
<!-- hata listesi -->
<div class="alert alert-danger" ng-show="errors.show">
...
</div>
<!-- müşteri listesi -->
<div class="alert alert-info">
<div class="row" ng-show="clients.show">
<div class="col-md-4">
<h2 translate="{{clients.title}}"></h2>
<select data-style="btn-primary" id="selectpickerClients" select-enable2="" ng-if="clients.data">
<option ng-repeat="client in clients.data" value="{{client.id}}">
{{client.titre}} {{client.prenom}} {{client.nom}}
</option>
</select>
</div>
</div>
</div>
<!-- doktor listesi -->
<div class="alert alert-info">
<div class="row" ng-show="medecins.show">
<div class="col-md-4">
<h2 translate="{{medecins.title}}"></h2>
<select data-style="btn-primary" id="selectpickerMedecins" select-enable2="" ng-if="medecins.data">
<option ng-repeat="medecin in medecins.data" value="{{medecin.id}}">
{{medecin.titre}} {{medecin.prenom}} {{medecin.nom}}
</option>
</select>
</div>
</div>
</div>
</div>
...
<script type="text/javascript" src="rdvmedecins-09.js"></script>
<!-- yönergeler -->
<script type="text/javascript" src="selectEnable2.js"></script>
- 19-23. satırlar: müşteri açılır listesi;
- 19. satır: [selectEnable2] yönergesi uygulanır ([select-enable2] özniteliği);
- 19. satır: Yalnızca [clients.data] boş değilse;
- satır 19: açılır liste, [id="selectpickerClients"] özniteliği ile tanımlanır;
- satır 33-37: doktorların açılır listesi;
- satır 33: [selectEnable2] yönergesi uygulanır ([select-enable2] özniteliği);
- satır 33: Yalnızca [medecins.data] boş değilse;
- satır 33: açılır liste, [id="selectpickerMedecins"] özniteliği ile tanımlanır;
- satır 43: JS adlı yeni bir dosya içe aktarılır;
- 45. satır: yeni yönergedeki JS dosyası içe aktarılır;
3.7.11.3. [selectEnable2] yönergesi
[selectEnable2] yönergesinin kodu şöyledir:
angular.module("rdvmedecins").directive('selectEnable2', ['$timeout', 'utils', function ($timeout, utils) {
return {
link: function (scope, element, attrs) {
utils.debug("directive selectEnable2 attrs", attrs);
$timeout(function () {
$('#' + attrs['id']).selectpicker();
})
}
}
}]);
- 4. satır: Kodun işleyişini anlamak için [attrs] parametresinin değerini görüntüleyelim. Müşteri listesi için attrs['id']='selectpickerClients' olduğunu göreceğiz;
- 6. satır: DOM içinde [id='x'] öğesini bulmak için [$('#x')] yazılır. Dolayısıyla, müşteri listesini bulmak için [$('#selectpickerClients')] yazılmalıdır. Bu, [$('#' + attrs['id'])] sözdizimi ile elde edilir;
Dolayısıyla [selectEnable2] yönergesi, uygulandığı HTML öğesinin özniteliklerinden biri tarafından taşınan bilgiyi kullanır.
3.7.11.4. C denetleyicisi
C denetleyicisi, JS [rdvmedecins-09.js] dosyasında bulunur ve aşağıdaki yapıya sahiptir:
// denetleyici
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao',
function ($scope, utils, config, dao) {
// ------------------- şablon başlatma
// bekleme mesajı
$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time: 3000};
// giriş bilgileri
$scope.server = {url: 'http://localhost:8080', kullanıcı adı: 'admin', şifre: 'admin'};
// hatalar
$scope.errors = {show: false, model: {}};
// doktorlar
$scope.medecins = {title: config.listMedecins, show: false, model: {}};
// müşteriler
$scope.clients = {title: config.listClients, show: false, model: {}};
// genel asenkron görev
var task;
// ---------------------------------------------------- görünüm başlatma
// UI güncelleniyor
$scope.waiting.visible = true;
$scope.clients.show = false;
$scope.medecins.show = false;
$scope.errors.show = false;
// önce müşteriler, ardından doktorlar sorgulanıyor
getClients().then(function () {
getMedecins();
});
// müşteri listesi
function getClients() {
...
}
// doktor listesi
function getMedecins() {
...
}
// bekleyen iptal
function cancel() {
...
}
} ]);
- 26-28. satırlar: önce müşteriler, ardından doktorlar sorgulanır;
3.7.11.5. Testler
Bu yeni sürümü test edin.
3.7.12. Örnek 12: [list] yönergesi
Önceki örnekle aynı örneği ele alıyoruz, ancak bir yönerge kullanarak HTML kodunu sadeleştirmek istiyoruz. Şu anda elimizde şu HTML kodu var:
<!-- müşteri listesi -->
<div class="alert alert-info">
<div class="row" ng-show="clients.show">
<div class="col-md-4">
<h2 translate="{{clients.title}}"></h2>
<select data-style="btn-primary" id="selectpickerClients" select-enable2="" ng-if="clients.data">
<option ng-repeat="client in clients.data" value="{{client.id}}">
{{client.titre}} {{client.prenom}} {{client.nom}}
</option>
</select>
</div>
</div>
</div>
<!-- doktor listesi -->
<div class="alert alert-info">
<div class="row" ng-show="medecins.show">
<div class="col-md-4">
<h2 translate="{{medecins.title}}"></h2>
<select data-style="btn-primary" id="selectpickerMedecins" select-enable2="" ng-if="medecins.data">
<option ng-repeat="medecin in medecins.data" value="{{medecin.id}}">
{{medecin.titre}} {{medecin.prenom}} {{medecin.nom}}
</option>
</select>
</div>
</div>
</div>
14-26. satırlar, 1-13. satırlarla aynıdır. Bu satırlar, müşteriler yerine doktorlar için geçerlidir. Şu şekilde yazabilmek istiyoruz:
<!-- müşteri listesi -->
<list model="clients" ng-if="clients.show"></list>
<!-- doktor listesi -->
<list model="medecins" ng-if="medecins.show"></list>
Bu kod, şimdi oluşturacağımız yeni bir [list] yönergesini gerektirir.
3.7.12.1. [list] yönergesi
[list] yönergesi, JS [list.js] dosyasına yerleştirilir. Kodu şöyledir:
angular.module("rdvmedecins")
.directive("list", ['utils', '$timeout', function (utils, $timeout) {
// dönürülen yönerge örneği
return {
// HTML öğesi
restrict: "E",
// parça URL'si
templateUrl: "list.html",
// yönergenin her örneğine özgü kapsam
scope: true,
// belgeyle bağlantı işlevi
link: function (scope, element, attrs) {
utils.debug("directive list attrs", attrs);
scope.model = scope[attrs['model']];
utils.debug("directive list model", scope.model);
$timeout(function () {
$('#' + scope.model.id).selectpicker();
})
}
}
}]);
- 2. satır: 'list' adlı bir yönergeyi tanımlar;
- 6. satır: [restrict] özniteliği, yönergenin kullanım şekillerini belirler. [restrict: "E"], [list] yönergesinin HTML <list ...>...</list> öğesi olarak kullanılabilir. [restrict: "A"], [list] yönergesinin, örneğin <div ... list='...'> gibi bir öznitelik olarak kullanılabileceği anlamına gelir. [restrict: "AE"], [list] yönergesinin hem öznitelik hem de öğe olarak kullanılabileceğini belirtir;
- 8. satır: [templateUrl] özniteliği, etiketle karşılaşıldığında kullanılacak HTML parçacığının adını belirtir. Bu parçacık, etiketin gövdesini oluşturacaktır;
- 10. satır: [scope] özniteliği, yönergenin şablonunun kapsamını belirler. [scope: true], iki <list> türündeki öğenin her birinin kendi şablonuna sahip olacağı anlamına gelir. Varsayılan olarak (kapsam başlatılmamışsa), şablonlarını paylaşırlar;
- 12. satır: Daha önce birkaç kez kullandığımız [link] işlevi;
Yukarıdaki kodu anlamak için, yönergenin nasıl kullanılacağını hatırlamak gerekir:
<!-- müşteri listesi -->
<list model="clients" ng-if="clients.show"></list>
<!-- doktor listesi -->
<list model="medecins" ng-if="medecins.show"></list>
[list] yönergesi, HTML <list> öğesi olarak kullanılır. Bu öğenin iki özniteliği vardır:
- [model]: Bu özniteliğin değeri, [list] yönergesinin bulunduğu V görünümündeki M modelinin öğesi olacaktır. Bu öğe, yönergenin modelini besleyecektir;
- [ng-if]: Görüntülenecek bir şey yoksa, yönergenin HTML kodunun oluşturulmamasını sağlar;
Yönergenin [link] işlevinin koduna geri dönelim:
link: function (scope, element, attrs) {
utils.debug("directive list attrs", attrs);
scope.model = scope[attrs['model']];
utils.debug("directive list model", scope.model);
$timeout(function () {
$('#' + scope.model.id).selectpicker();
})
}
Bu JS kodunu, yönergeyi kullanan HTML koduyla ilişkilendirelim:
<list model="clients" ng-if="clients.show"></list>
- 3. satır: attrs['model']'in değeri burada 'clients' olarak belirtilmiştir;
- 3. satır: scope[attrs['model']]'in değeri scope['clients']'tir ve dolayısıyla [$scope.clients]'i temsil eder, yani görünüm modelindeki [clients] alanını. Bu alanın değeri {id: '...', data: [client1, client2, ...], show: ..., title: '...'} olacaktır;
- 3. satır: Direktif modeline bir [model] alanı eklenir. Bu alan, içinde bulunduğu görünüm modelinden miras almıştır. Dolayısıyla, görünümde de bulunabilecek olası bir [model] alanıyla çakışmayı önlemek gerekir. Burada bir çakışma olmayacaktır;
- 4. satır: Kodu daha iyi anlayabilmek için [scope.model] gösterilir;
- 5-7. satırlar: Daha önce karşılaştığımız bir kodla karşılaşıyoruz. Aradaki fark, bileşenin id değerinin daha önce attrs['id'] özniteliğinde yer almasıydı. Burada ise [scope.model.id] içinde yer alacaktır;
Şimdi, yönerge tarafından oluşturulan HTML koduna bakalım. Yönergenin [templateUrl: "list.html"] özniteliği nedeniyle, bu kodu [list.html] dosyasında aramamız gerekiyor:
<!-- müşteri veya doktor listesi -->
<div class="alert alert-info" ng-show="model.show">
<div class="row">
<div class="col-md-4">
<h2 translate="{{model.title}}"></h2>
<select data-style="btn-primary" id="{{model.id}}" ng-if="model.data">
<option ng-repeat="element in model.data" value="{{element.id}}">
{{element.titre}} {{element.prenom}} {{element.nom}}
</option>
</select>
</div>
</div>
</div>
- Bu kodu okumak için akılda tutulması gereken ilk şey, yönergenin [{id :'...', data:[client1, client2, ...], show : ..., title :'...'}] biçiminde bir [scope.model] nesnesi oluşturduğudur. Bu [model] nesnesi (scope, HTML kodunda örtük olarak bulunur), yönergenin HTML kodu tarafından kullanılır;
- 2. satır: Direktif tarafından oluşturulan görünümü göstermek/gizlemek için [model.show] kullanılır;
- 5. satır: Başlık eklemek için [model.title] kullanılır;
- 6. satır: <select> etiketine bir kimlik atamak için [model.id] kullanılır. Bu id, yönergenin JS kodu tarafından kullanılır;
- 6. satır: Yalnızca görüntülenecek veriler varsa <select> öğesini oluşturmak için [model.data] kullanılır;
- 7-9. satırlar: Açılır menü öğelerini oluşturmak için [model.data] kullanılır;
3.7.12.2. HTML kodu
[app-22.html] uygulamasının HTML kodu şöyledir:
<div class="container">
<h1>Rdvmedecins - v1</h1>
<!-- bekleme mesajı -->
<div class="alert alert-warning" ng-show="waiting.visible">
...
</div>
<!-- hata listesi -->
<div class="alert alert-danger" ng-show="errors.show">
...
</div>
<!-- müşteri listesi -->
<list model="clients" ng-if="clients.show"></list>
<!-- doktor listesi -->
<list model="medecins" ng-if="medecins.show"></list>
</div>
...
<script type="text/javascript" src="rdvmedecins-10.js"></script>
<!-- yönergeler -->
<script type="text/javascript" src="list.js"></script>
- 22. satır: yönergedeki JS kodunu eklemeyi unutmayın;
3.7.12.3. C denetleyicisi
C denetleyicisinde çok az değişiklik vardır:
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao',
function ($scope, utils, config, dao) {
// ------------------- şablon başlatma
...
// doktorlar
$scope.medecins = {title: config.listMedecins, show: false, id: 'medecins'};
// müşteriler
$scope.clients = {title: config.listClients, show: false, id: 'clients'};
...
- 7. ve 9. satırlarda, doktor ve müşteri şablonlarına [id] özniteliğini ekliyoruz;
3.7.12.4. Testler
Testler, önceki örnekte olduğu gibi aynı sonuçları veriyor.
3.7.13. Örnek 13: Bir yönergenin şablonunun güncellenmesi
Yönergeleri incelemeye devam ediyoruz ve açılır liste örneğini kullanmaya devam ediyoruz. Burada, açılır listenin içeriği değiştiğinde [list] yönergesinin davranışını incelemek istiyoruz.
3.7.13.1. V görünümleri
Farklı görünümler şunlardır:
![]() |
- [1]'te, ilk olarak müşteri listesi isteniyor;
![]() |
- [2]'te, müşteri listesi ikinci kez istenir. Bu ikinci liste, ilk liste olan [3] ile birleştirilir. Bu örnekte, [Bootstrap select] bileşeninin güncellenmesini inceleyeceğiz.
3.7.13.2. HTML sayfası
HTML [app-23.html] sayfası, [app-22.html] sayfasının kopyalanmasıyla elde edilir ve ardından şu şekilde değiştirilir:
<div class="container">
<h1>Rdvmedecins - v1</h1>
<!-- bekleme mesajı -->
<div class="alert alert-warning" ng-show="waiting.visible">
...
</div>
<!-- hata listesi -->
<div class="alert alert-danger" ng-show="errors.show">
...
</div>
<!-- düğme -->
<div class="alert alert-warning">
<button class="btn btn-primary" ng-click="getClients()">{{clients.title|translate}}</button>
</div>
<!-- müşteri listesi -->
<list2 model="clients" ng-if="clients.show"></list2>
</div>
...
<script type="text/javascript" src="rdvmedecins-11.js"></script>
<!-- yönergeler -->
<script type="text/javascript" src="list2.js"></script>
Önceki uygulamaya göre yapılan değişiklikler şunlardır:
- 15-17. satırlar: bir düğme eklenmiştir;
- 20. satır: yeni bir [list2] yönergesinin kullanılması;
- 23. satır: yeni bir JS dosyasının kullanılması;
- 25. satır: [list2] yönergesinden JS dosyasının içe aktarılması;
3.7.13.3. [list2] yönergesi
[list2.js] içindeki [list2] yönergesi şöyledir:
angular.module("rdvmedecins")
.directive("list2", ['utils', '$timeout', function (utils, $timeout) {
// dönürülen yönerge örneği
return {
// HTML öğesi
restrict: "E",
// parça URL'si
templateUrl: "list.html",
// yönergenin her örneğine özgü kapsam
scope: true,
// belgeyle bağlantı işlevi
link: function (scope, element, attrs) {
utils.debug('directive list2');
scope.model = scope[attrs['model']];
$timeout(function () {
$('#' + scope.model.id).selectpicker('refresh');
})
}
}
}]);
[list] yönergesiyle arasındaki tek fark 16. satırdır: [selectpicker('refresh')] yöntemi ile [Bootstrap-select] bileşeninden kendini yenilemesi istenir. Bunun arkasındaki fikir, kullanıcı her yeni müşteri listesi talep ettiğinde açılır listenin yenilenmesidir. Bu şekilde çalışmayacaktır, ancak temel fikir budur.
3.7.13.4. C denetleyicisi
Denetleyici, [rdvmedecins-10.js] dosyasının kopyalanmasıyla elde edilen [rdvmedecins-11.js] dosyasında yer almaktadır:
// müşteriler
$scope.clients = {title: config.listClients, show: false, id: 'clients', data: []};
...
// müşteri listesi
$scope.getClients = function getClients() {
// UI güncelleniyor
$scope.waiting.visible = true;
$scope.errors.show = false;
// müşteri listesi isteniyor;
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password, config.urlSvrClients);
var promise = task.promise;
// önceki çağrının sonucunu analiz ediyoruz
promise = promise.then(function (result) {
// sonuç={hata: 0, veriler: [client1, client2, ...]}
// sonuç={hata: n, mesajlar: [msg1, msg2, ...]}
if (result.err == 0) {
// alınan verileri yeni bir modele aktarıyoruz, böylece görünümün yenilenmesini sağlıyoruz
$scope.clients = {title: $scope.clients.title, data: $scope.clients.data.concat(result.data), show: $scope.clients.show, id: $scope.clients.id};
// UI güncellenir
$scope.clients.show = true;
$scope.waiting.visible = false;
} else {
// Müşteri listesini alırken hatalar oluştu
$scope.errors = { title: config.getClientsErrors, messages: utils.getErrors(result), show: true, model: {}};
// UI güncelleniyor
$scope.waiting.visible = false;
}
});
}
- 1. satır: [clients.data]'te tabloların birleştirilmesini sağlamak için bu nesne boş bir tablo ile başlatılır;
- 18. satır: Yeni müşteri listesi, [clients.data] dizisinde zaten bulunan listelerle birleştirilir;
Daha önce şöyle yazmıştık:
Şimdi şöyle yazıyoruz:
Bu kodu anlamak için, [list2] yönergesi durumunda M şablonunun V görünümünde nasıl kullanıldığını hatırlamak gerekir:
<!-- müşteri listesi -->
<list2 model="clients" ng-if="clients.show"></list2>
[list2] yönergesi tarafından kullanılan şablon [clients]'tir. Bu yönerge, yalnızca V görünümündeki M şablonunda [clients] değiştiğinde yeniden değerlendirilecektir. Değişiklik için akla gelen ilk fikir şunu yazmaktır:
yazmak olur; bu, yeni müşteri listesinin öncekilere eklenmesi gerektiğini hesaba katar. Böylece, [clients.data] değiştirilir, ancak [clients] değiştirilmez. Javascript’in inceliklerini tam olarak bilmiyorum ama [clients]’in, tıpkı [clients.data] gibi, bir işaretçi olması şaşırtıcı olmaz. [clients.data] işaretçisi değiştirildiğinde, [clients] işaretçisi değişmez. Bu durumda [list2] yönergesi yeniden değerlendirilmez. Uygulamayı hata ayıklarken (Chrome'da F12) gerçekten de bunu gözlemliyoruz.
Şu şekilde yazıldığında:
$scope.clients = {title: $scope.clients.title, data: $scope.clients.data.concat(result.data), show: $scope.clients.show, id: $scope.clients.id};
[$scope.clients]'in yeni bir değer aldığından emin oluruz. [$scope.clients] işaretçisi yeni bir nesneye işaret ediyor. Bu durumda [list2] yönergesi yeniden değerlendirilmelidir. Ancak yine de aranan sonuç elde edilemiyor. Müşteri listesini iki kez talep ettiğimizde ortaya çıkan ekran görüntülerine bakalım:
![]() |
- [1]'te sekiz yerine sadece dört öğe var;
- [2]'te bu dört öğe bir [select] içinde yer alıyor, ancak bu öğe gizlenmiş durumda (style='display: none');
![]() |
- [3]'te, dört müşteri başka bir mimari olan HTML'te bulunur ve kullanıcı açılır listeye tıkladığında gördüğü budur;
Son olarak, konsol günlükleri şu bilgiyi vermektedir:
- 1. satır: [dao] hizmeti başlatılır;
- 2. satır: [dao] hizmeti ilk müşteri listesini alır;
- satır 3: [list2] yönergesi yürütülür;
- satır 4: [dao] hizmeti ikinci bir müşteri listesi alır;
- satırın görüntülenmesi, yönergedeki aşağıdaki koddan kaynaklanmaktadır:
link: function (scope, element, attrs) {
utils.debug('directive list2');
...
}
[list2] yönergesinin yaşam döngüsünü inceleyelim:
- 1. ve 2. satırlar arasında, görünüm ilk kez görüntülenmiş olmasına rağmen yönerge etkinleştirilmemiştir. Bunun nedeni, V görünümündeki [ng-if="clients.show"] özniteliğidir:
<list2 model="clients" ng-if="clients.show"></list2>
- 3. satır: ilk doktor listesi alındıktan sonra, [clients.show] true değerine geçer ve yönerge etkinleştirilir;
- ikinci müşteri listesi alındıktan sonra, [list2] yönergesinin kodunun çağrılmadığı görülüyor. Bu nedenle ikinci liste görüntülenmiyor;
Bu sorunu çözmek için [list2] yönergesini şu şekilde değiştiriyoruz:
angular.module("rdvmedecins")
.directive("list2", ['utils', '$timeout', function (utils, $timeout) {
// dönüşlenen yönergenin örneği
return {
// HTML öğesi
restrict: "E",
// parça URL'si
templateUrl: "list.html",
// yönergenin her örneğine özgü kapsam
scope: true,
// belgeyle bağlantı işlevi
link: function (scope, element, attrs) {
// attrs["model"] her değiştiğinde, yönergenin şablonu da değişmelidir
scope.$watch(attrs["model"], function (newValue) {
utils.debug("directive list2 newValue", newValue);
// yönergenin şablonu güncellenir
scope.model = newValue;
$timeout(function () {
$('#' + scope.model.id).selectpicker('refresh');
})
});
}
}
}]);
- 14. satır: [scope.$watch] işlevi, modeldeki bir değeri izlememizi sağlar. Sözdizimi [scope.$watch('var'), f] şeklindedir; burada [var], modeldeki bir değişkenin tanımlayıcısıdır ve f, bu değişkenin değeri değiştiğinde çalıştırılacak işlevdir. Burada, [clients] değişkenini izlemek istiyoruz. Dolayısıyla [scope.$watch('clients')] yazmalıyız. attrs['model']='clients' olarak ayarlandığından, [scope.$watch(attrs["model"], function (newValue)] yazıyoruz;
- 14. satır: [scope.$watch] işlevinin ikinci parametresi, gözlemlenen değişkenin değeri değiştiğinde çalıştırılacak işlevdir. [newValue] parametresi, değişkenin yeni değeridir; dolayısıyla bizim için modelin [clients] değişkeninin yeni değeridir;
- 17. satır: Bu yeni değer, yönerge modelinin [model] alanına atanır;
Bu değişiklik yapıldıktan sonra, günlükler değişir:
![]() |
Yukarıda, ikinci müşteri listesini aldıktan sonra [list2] yönergesinin yeniden başarıyla yürütüldüğü görülmektedir; bu durum, [2] sonucu ile teyit edilmektedir.
3.7.14. Örnek 14: [waiting] ve [errors] yönergeleri
Önceki uygulamadaki HTML koduna geri dönelim:
<div class="container">
<h1>Rdvmedecins - v1</h1>
<!-- bekleme mesajı -->
<div class="alert alert-warning" ng-show="waiting.visible">
...
</div>
<!-- hata listesi -->
<div class="alert alert-danger" ng-show="errors.show">
...
</div>
<!-- düğme -->
<div class="alert alert-warning">
<button class="btn btn-primary" ng-click="getClients()">{{clients.title|translate}}</button>
</div>
<!-- müşteri listesi -->
<list2 model="clients" ng-if="clients.show"></list2>
</div>
- 5-7. satırlar: bekleme mesajı;
- 10-12. satırlar: hata mesajı;
Bu iki mesajın HTML kodlarını yönergelere yerleştirmeye karar verdik.
3.7.14.1. Yeni kod HTML
Yeni kod HTML [app-24.html] şu şekildedir:
<div class="container">
<h1>Rdvmedecins - v1</h1>
<!-- bekleme mesajı -->
<waiting model="waiting"></waiting>
<!-- hata listesi -->
<errors model="errors"></errors>
<!-- düğme -->
<div class="alert alert-warning">
<button class="btn btn-primary" ng-click="getClients()">{{clients.title|translate}}</button>
</div>
<!-- müşteri listesi -->
<list2 model="clients" ng-if="clients.show"></list2>
</div>
...
<script type="text/javascript" src="rdvmedecins-12.js"></script>
<!-- yönergeler -->
<script type="text/javascript" src="list2.js"></script>
<script type="text/javascript" src="errors.js"></script>
<script type="text/javascript" src="waiting.js"></script>
- 5. satır: bekleme mesajı için yönerge;
- 8. satır: hata mesajı için yönerge;
- 19. satır: uygulamayla ilişkili yeni JS dosyası;
- 21-23. satırlar: üç yönergenin JS dosyaları;
3.7.14.2. [waiting] yönergesi
[waiting] yönergesinin JS kodu, aşağıdaki [waiting.js] dosyasında yer almaktadır:
angular.module("rdvmedecins")
.directive("waiting", ['utils', function (utils) {
// dönürülen yönerge örneği
return {
// HTML öğesi
restrict: "E",
// parça URL'si
templateUrl: "waiting.html",
// yönergenin her örneğine özgü kapsam
scope: true,
// belgeyle bağlantı işlevi
link: function (scope, element, attrs) {
// attr["model"] her değiştiğinde, sayfa şablonu da değişmelidir
scope.$watch(attrs["model"], function (newValue) {
utils.debug("[waiting] watch newValue", newValue);
scope.model = newValue;
});
}
}
}]);
Bu kod, daha önce incelenen [list2] yönergesindeki kodla aynı mantığı izler.
- satırda, aşağıdaki [waiting.html] dosyasına atıfta bulunulmaktadır:
<div class="alert alert-warning" ng-show="model.show">
<h1>{{ model.title.text | translate:model.title.values}}
<button class="btn btn-primary pull-right" ng-click="model.cancel()">{{'cancel'|translate}}</button>
<img src="assets/images/waiting.gif" alt=""/>
</h1>
</div>
Uygulamadaki JS kodunda, bu HTML koduna ait [$scope.waiting] şablonu şu şekilde tanımlanacaktır:
// bekleme mesajı
$scope.waiting = {title: {text: config.msgWaiting, values: {}}, show: false, cancel: cancel, time: 3000};
3.7.14.3. [errors] yönergesi
[errors] yönergesinin JS kodu, aşağıdaki [errors.js] dosyasında yer almaktadır:
angular.module("rdvmedecins")
.directive("errors", ['utils', function (utils) {
// döndürülen yönergenin örneği
return {
// HTML öğesi
restrict: "E",
// parça URL'si
templateUrl: "errors.html",
// yönergenin her örneğine özgü kapsam
scope: true,
// belgeyle bağlantı işlevi
link: function (scope, element, attrs) {
// attr["model"] her değiştiğinde, sayfa şablonu da değişmelidir
scope.$watch(attrs["model"], function (newValue) {
utils.debug("[errors] watch newValue", newValue);
scope.model = newValue;
});
}
}
}]);
Bu kod, daha önce incelenen [list2] yönergesindeki kodla aynı mantığı izler.
- satırda, aşağıdaki [errors.html] dosyasına atıfta bulunulmaktadır:
<div class="alert alert-danger" ng-show="model.show">
{{model.title.text|translate:model.title.values}}
<ul>
<li ng-repeat="message in model.messages">{{message|translate}}</li>
</ul>
</div>
Uygulamadaki JS kodunda, bu HTML kodunun [$scope.errors] şablonu şu şekilde tanımlanacaktır:
// müşteri listesini alırken hatalar oluştu
$scope.errors = { title: { text: config.getClientsErrors, values: {}}, messages: utils.getErrors(result), show: true, model: {}};
3.7.15. Örnek 15: Gezinme
Şimdiye kadar tek sayfalı uygulamalar kullandık. Bu örnekte, çok sayfalı uygulamaları ve bu sayfalar arasında gezinmeyi ele alacağız.
3.7.15.1. Uygulamanın V görünümleri
![]() |
- [1]'te, 1 numaralı görünümün URL'i;
- [2]'te, içeriği;
- [3]'te, 2. sayfaya geçilir;
- [4]'te, 2 numaralı görünüm;
- [5]'te, 3. sayfaya geçilir;
![]() |
- [6]'te, 3 numaralı görünüm;
- [7]'te, 1. sayfaya geçilir;
- [8]'te, 1 numaralı görünüme geri dönülür;
3.7.15.2. Kodun düzenlenmesi
Kodun yeni bir düzenlemesine başlıyoruz:
![]() |
- uygulamanın görünümleri [views] klasörüne yerleştirilecek;
- uygulama modülü [modules] klasörüne yerleştirilecek;
- uygulamanın denetleyicileri [controllers] klasörüne yerleştirilecek;
Aynı şekilde, son sürümde:
- hizmetler [services] klasörüne yerleştirilecektir;
- yönergeler [directives] klasörüne yerleştirilecektir;
3.7.15.3. Görünümler konteyneri
[views] klasöründeki görünümler, aşağıdaki [app-25.html] konteynerinde görüntülenecektir:
<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
...
</head>
<body>
<div class="container" ng-controller="mainCtrl">
<!-- gezinme çubuğu -->
<ng-include src="'views/navbar.html'"></ng-include>
<!-- mevcut görünüm -->
<ng-view></ng-view>
</div>
...
<!-- modül -->
<script type="text/javascript" src="modules/rdvmedecins-13.js"></script>
<!-- denetleyiciler -->
<script type="text/javascript" src="controllers/mainController.js"></script>
<script type="text/javascript" src="controllers/page1Controller.js"></script>
<script type="text/javascript" src="controllers/page2Controller.js"></script>
<script type="text/javascript" src="controllers/page3Controller.js"></script>
</body>
</html>
- 7. satır: Konteynerin gövdesi [mainCtrl] tarafından kontrol edilir;
- 9. satır: [ng-include] yönergesi, harici bir HTML dosyasının (burada bir gezinme çubuğu) eklenmesini sağlar;
- 12. satır: Konteyner tarafından görüntülenen farklı görünümler, [ng-view] yönergesi içinde görüntülenir. Sonuç olarak, şu öğeleri görüntüleyen bir konteyner elde ederiz:
- her zaman aynı gezinme çubuğunu (9. satır);
- 12. satırda farklı görünümler;
- 16-22. satırlar: JS dosyaları, [rdvmedecins-13.js] uygulama modülünden ve denetleyicilerinden içe aktarılır;
3.7.15.4. Uygulama modülü
[rdvmedecins-13.js] dosyası, uygulama modülünü ve görünümler arasındaki yönlendirmeyi tanımlar:
// --------------------- Angular modülü
angular.module("rdvmedecins", [ 'ngRoute' ]);
angular.module("rdvmedecins").config(["$routeProvider", function ($routeProvider) {
// ------------------------ yönlendirme
$routeProvider.when("/page1",
{
templateUrl: "views/page1.html",
controller: 'page1Ctrl'
});
$routeProvider.when("/page2",
{
templateUrl: "views/page2.html",
controller: 'page2Ctrl'
});
$routeProvider.when("/page3",
{
templateUrl: "views/page3.html",
controller: 'page3Ctrl'
});
$routeProvider.otherwise(
{
redirectTo: "/page1"
});
}]);
- 1. satır: [rdvmedecins] modülü tanımlanır. Bu modül, [angular-route.min.js] kütüphanesi tarafından sağlanan [ngRoute] modülüne bağımlıdır. 6-24. satırlarda tanımlanan yönlendirmeyi sağlayan modül budur;
- 4. satır: [rdvmedecins] modülünün [config] işlevi tanımlanmaktadır. Bu fonksiyonun, herhangi bir hizmet örneklenmesinden önce yürütüldüğünü hatırlatmak isteriz. Bu, modülün bir yapılandırma fonksiyonudur. Burada, modülün yönlendirmesi yapılandırılmaktadır. Bu, [ngRoute] modülü tarafından sağlanan [$routeProvider] nesnesi aracılığıyla yapılır;
- 6-10. satırlar: Kullanıcı URL [/page1]'i talep ettiğinde görüntülenecek görünümü tanımlar. Bu, uygulama içi bir yönlendirmedir. URL aslında [/rdvmedecins-angular-v1/app-21.html#/page1]'tir. Görüldüğü gibi, her zaman [/rdvmedecins-angular-v1/app-21.html] konteynerine ait URL kullanılır, ancak # karakterinin arkasında ek bir bilgi bulunur. Angular yönlendirmesi bu ek bilgiyi yönetir;
- 8. satır: konteynerin [ng-view] direktifine eklenecek HTML fragmanını belirtir:
- 9. satır: bu parçanın denetleyicisinin adını belirtir;
- 11-15. satırlar: kullanıcı URL [/page2]'i talep ettiğinde görüntülenecek görünümü tanımlar;
- 16-20. satırlar: kullanıcı URL [/page3]'i talep ettiğinde görüntülenecek görünümü tanımlar;
- satır 21-24: istenen URL, önceki üç seçenekten biri olmadığında uygulanacak yönlendirmeyi tanımlar (otherwise, satır 21);
- satır 23: URL [/page1]'e, yani satır 6-10'da tanımlanan görünüme yönlendirme;
3.7.15.5. Görünüm konteynerinin denetleyicisi
Görünüm konteynerinin bir denetleyici tanımladığını görmüştük:
<div class="container" ng-controller="mainCtrl">
[mainCtrl] denetleyicisi, [mainController.js] dosyasında tanımlanmıştır:
// denetleyici
angular.module("rdvmedecins")
.controller('mainCtrl', ['$scope', '$location',
function ($scope, $location) {
// sayfa şablonları
$scope.page1 = {};
$scope.page2 = {};
$scope.page3 = {};
// genel şablon
var main = $scope.main = {};
main.text = "[Modèle global]";
// görünümde sunulan yöntemler
main.showPage1 = function () {
$location.path("/page1");
};
main.showPage2 = function () {
$location.path("/page2");
};
main.showPage3 = function () {
$location.path("/page3");
}
}]);
- 3. satır: [mainCtrl] denetleyicisi, [ngRoute] yönlendirme modülü tarafından sağlanan [$location] nesnesine ihtiyaç duyar. Bu nesne, görünüm değiştirmeye olanak tanır (16., 19. ve 22. satırlar);
Tekrar konteyner koduna dönelim:
<div class="container" ng-controller="mainCtrl">
<!-- gezinme çubuğu -->
<ng-include src="'views/navbar.html'"></ng-include>
<!-- geçerli görünüm -->
<ng-view></ng-view>
</div>
- [mainCtrl] denetleyicisi, 1-7 numaralı alanların şablonunu oluşturur;
- 6. satırda yer alan görünümün de bir denetleyicisi vardır. Örneğin, [page1] görünümü, [page1Ctrl] denetleyicisine sahiptir. Bu denetleyici, 6. satırda görüntülenen alanın modelini oluşturur. Dolayısıyla bu alanda iki model bulunur:
- [mainCtrl] denetleyicisi tarafından oluşturulan model;
- [page1Ctrl] denetleyicisi tarafından oluşturulan model;
Şablonların miras alma özelliği vardır. 6. satırda gösterilen görünümde, [mainCtrl] ve [pagexCtrl] denetleyicilerinin şablonları ikisi de görülebilir. Bu şablonlardaki iki değişken aynı ada sahipse, biri diğerini gizler. Bu ad çakışmasını önlemek için, dört farklı ad altında dört şablon oluşturuyoruz:
konteyner | mainCtrl | el | 11 |
sayfa1 | page1Ctrl | sayfa1 | 7 |
sayfa 2 | page2Ctrl | sayfa2 | 8 |
sayfa 3 | page3Ctrl | sayfa3 | 9 |
- 12. satır: [main] şablonunda bir [text] öğesini tanımlar;
7-11. satırların çok özel bir sonucu vardır: [mainCtrl] denetleyicisinin [$scope] öğesini tanımlarlar ve bu öğe içinde dört adet [main, page1, page2, page3] değişkeni oluştururlar. Bu dört değişken, sırasıyla konteyner ve konteynerin sırayla içereceği üç görünümün şablonları olarak kullanılacaktır.
3.7.15.6. Gezinti çubuğu
Gezinti çubuğu, konteyner içinde şu şekilde tanımlanır:
<div class="container" ng-controller="mainCtrl">
<!-- gezinme çubuğu -->
<ng-include src="'views/navbar.html'"></ng-include>
<!-- geçerli görünüm -->
<ng-view></ng-view>
</div>
Gezinme çubuğu 3. satırda tanımlanmıştır. Bu, gezinme çubuğunun yalnızca [main] şablonunu tanıdığı anlamına gelir. Kod şöyledir:
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">RdvMedecins</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active">
<a href="">
<span ng-click="main.showPage1()">Page 1</span>
</a>
</li>
<li class="active">
<a href="">
<span ng-click="main.showPage2()">Page 2</span>
</a>
</li>
<li class="active">
<a href="">
<span ng-click="main.showPage3()">Page 3</span>
</a>
</li>
</ul>
</div>
</div>
</div>
- 16., 21. ve 26. satırlarda, [main] modeline ait yöntemler kullanılmaktadır;
- 16. satır: [Page1] bağlantısına tıklandığında [$scope.main.showPage1] yönteminin yürütülmesi başlatılır. Bu yöntem, [mainCtrl] denetleyicisinde şu şekilde tanımlanmıştır:
// genel şablon
var main = $scope.main = {};
main.text = "[Modèle global]";
// görüneye açık yöntemler
main.showPage1 = function () {
$location.path("/page1");
};
- 6. satır: Yukarıdaki koddan, [main.showPage1] yönteminin aslında [$scope.main.showPage1] yöntemi olduğu anlaşılmaktadır. Dolayısıyla, yürütülecek olan yöntem budur;
- 7. satır: Uygulamadaki URL değiştirilerek [/page1] haline getirilir. Ana modülde tanımlanan yönlendirmeye geri dönelim:
$routeProvider.when("/page1",
{
templateUrl: "views/page1.html",
controller: 'page1Ctrl'
});
[views/page1.html] parçasının konteynere ekleneceğini ve denetleyicisinin [page1Ctrl] olduğunu görüyoruz.
3.7.15.7. [/page1] görünümü ve denetleyicisi
[views/page1.html] parçası şöyledir:
<h1>Page 1</h1>
<div class="alert alert-info">
<ul>
<li>Modèle global : {{main.text}}</li>
<li>Modèle local : {{page1.text}}</li>
</ul>
</div>
Hatırlayacağımız üzere, konteynere eklenen görünümde [main] modeli görülebilir. 4. satırda bunu doğrulamak istiyoruz. Ayrıca, [views/page1.html] parçasının denetleyicisi [page1Ctrl], bir [page1] şablonu tanımlar. 5. satırda kullanılan şablon budur.
[page1Ctrl] denetleyicisinin kodu şöyledir:
angular.module("rdvmedecins")
.controller('page1Ctrl', ['$scope',
function ($scope) {
// 1. sayfa şablonu
var page1=$scope.page1;
page1.text="[Modèle local dans page 1]";
}]);
- 2. satır: Buraya eklenen [$scope] boş değildir. [page1Ctrl] denetleyicisi, [mainCtrl] tarafından denetlenen bir kapsayıcıya eklenmiş bir alanı denetlediğinden, 2. satırdaki [$scope], [mainCtrl] denetleyicisi tarafından tanımlanan [$scope]'in öğelerini içerir. Bunu anlamak önemlidir. [mainCtrl] denetleyicisi tarafından tanımlanan [$scope], [main, page1, page2, page3]'in aşağıdaki öğelerini içerir. Bu, tüm görünümlerin modellerine erişimimiz olduğu anlamına gelir. Bu her zaman istenen bir durum olmayabilir, ancak burada durum böyledir. Angular istemcisinin son sürümünde, bu özelliği kullanarak görünümler arasında paylaşılması gereken bilgileri [main] modelinde depolayacağız. Burada, sunucu tarafındaki 'oturum' kavramına benzer bir kavramla karşılaşacağız;
- 6. satır: [$scope]'ten 1. sayfanın [page1] modelini alıyoruz ve ardından bununla çalışıyoruz (7. satır). Böylece şu görüntü elde edilir:
![]() |
[/page2] ve [/page3] görünümleri, [/page1] görünümüyle aynı şablon üzerine oluşturulmuştur (sayfa 240'taki ekran görüntülerine bakınız).
3.7.15.8. Gezinme Kontrolü
Şimdi gezinmeyi şu şekilde kontrol etmek istiyoruz: [page1 --> page2 --> page3 --> page1]. Böylece, kullanıcı 1. sayfada ([/page1]) bulunuyorsa ve tarayıcısına URL veya [/page3] yazarsa, bu gezinme kabul edilmemeli ve kullanıcı 1. sayfada kalmalıdır.
Bu sonucu elde etmek için sayfa denetleyicilerini şu şekilde değiştiriyoruz:
angular.module("rdvmedecins")
.controller('page1Ctrl', ['$scope', '$location',
function ($scope, $location) {
// gezinme izni var mı?
var main = $scope.main;
if (main.lastUrl && main.lastUrl != '/page3') {
// son URL'e geri dönülür
$location.path(main.lastUrl);
return;
}
// sayfanın URL'i kaydediliyor
main.lastUrl = '/page1';
// sayfa şablonu
var page1 = $scope.page1;
page1.text = "[Modèle local dans page 1]";
}]);
- 12. satır: Bir sayfa görüntülendiğinde, sayfanın URL değerini [main.lastUrl] şablonunda saklayacağız. Burada daha önce bahsettiğimiz kavramı kullanıyoruz: tüm görünümler tarafından paylaşılan bilgileri depolamak için [main] şablonunu kullanmak. Burada, en son görüntülenen URL'tir;
- 4-12. satırlardaki kod, üç görünüme de kopyalanıp uyarlanmıştır. Burada [/page1] görünümündeyiz;
- 5. satır: [main] modelini alıyoruz;
- 6. satır: [main.lastUrl] şablonu varsa ve [/page3]'ten farklıysa, gezinme engellenir (en son ziyaret edilen URL şablonu mevcuttur ve /page3 değildir);
- 8. satır: bu durumda en son ziyaret edilen URL sayfasına geri dönülür;
Bir deneme yapalım:
![]() |
- [1]'teyken, 1. sayfadayız ve 3. sayfadaki URL'i [2] olarak giriyoruz;
- [3]'te gezinme gerçekleşmedi ve 1. sayfadaki URL'e geri döndük;
3.7.16. Sonuç
Angular istemcisinin son sürümünde karşılaşacağımız tüm kullanım senaryolarını gözden geçirdik. Bu sürümü sunarken, uygulamanın işlevleri üzerinde, uygulama detaylarından daha fazla duracağız. Uygulama detayları için ise, o anda incelenen kullanım senaryosunu gösteren örneğe atıfta bulunmakla yetineceğiz.
3.8. Nihai Angular istemcisi
3.8.1. Proje Yapısı
Nihai proje şu şekildedir:
![]() |
![]() |
- [1], projenin tamamıdır. [app.html], uygulamanın ana sayfasıdır;
- [2] dosyası denetleyicileri içerir;
- [3] dosyasında, direktifler;
- [4] dosyasında, hizmetler ve uygulamanın Angular modülü [main.js];
- [5] dosyasında, [app.html] ana sayfasına eklenecek çeşitli görünümler;
3.8.2. Projenin bağımlılıkları
Projenin bağımlılıkları şunlardır:
![]() |
Bu farklı öğelerin işlevleri, 134. sayfadaki 3.4 numaralı paragrafta açıklanmıştır.
3.8.3. [app.html] ana sayfası
Ana sayfa şöyledir:
<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
<title>RdvMedecins</title>
<!-- META -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Angular client for RdvMedecins">
<meta name="author" content="Serge Tahé">
<!-- CSS -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css"/>
<link href="bower_components/bootstrap/dist/css/bootstrap-theme.min.css" rel="stylesheet"/>
<link href="bower_components/bootstrap-select/bootstrap-select.min.css" rel="stylesheet"/>
<link href="assets/css/rdvmedecins.css" rel="stylesheet"/>
<link href="assets/css/footable.core.min.css" rel="stylesheet"/>
</head>
<!-- denetleyici [appCtrl], şablon [app] -->
<body ng-controller="appCtrl">
<div class="container">
...
</div>
<!-- Bootstrap çekirdeği JavaScript ================================================== -->
<script type="text/javascript" src="bower_components/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bower_components/bootstrap-select/bootstrap-select.min.js"></script>
<script src="bower_components/footable/js/footable.js" type="text/javascript"></script>
<!-- AngularJS -->
<script type="text/javascript" src="bower_components/angular/angular.min.js"></script>
<script type="text/javascript" src="bower_components/angular-ui-bootstrap-bower/ui-bootstrap-tpls.min.js"></script>
<script type="text/javascript" src="bower_components/angular-route/angular-route.min.js"></script>
<script type="text/javascript" src="bower_components/angular-translate/angular-translate.min.js"></script>
<script type="text/javascript" src="bower_components/angular-base64/angular-base64.min.js"></script>
<!-- modüller -->
<script type="text/javascript" src="modules/main.js"></script>
<!-- hizmetler -->
<script type="text/javascript" src="services/config.js"></script>
<script type="text/javascript" src="services/dao.js"></script>
<script type="text/javascript" src="services/utils.js"></script>
<!-- yönergeler -->
<script type="text/javascript" src="directives/waiting.js"></script>
<script type="text/javascript" src="directives/errors.js"></script>
<script type="text/javascript" src="directives/footable.js"></script>
<script type="text/javascript" src="directives/debug.js"></script>
<script type="text/javascript" src="directives/list.js"></script>
<!-- denetleyiciler -->
<script type="text/javascript" src="controllers/appController.js"></script>
<script type="text/javascript" src="controllers/loginController.js"></script>
<script type="text/javascript" src="controllers/homeController.js"></script>
<script type="text/javascript" src="controllers/agendaController.js"></script>
<script type="text/javascript" src="controllers/resaController.js"></script>
</body>
</html>
- 18. satır: [appCtrl]'in ana sayfanın denetleyicisi olduğu unutulmamalıdır;
- 19-21. satırlar: ana sayfanın içeriği;
Bu içerik şöyledir:
<div class="container">
<!-- navigasyon çubukları -->
<ng-include src="'views/navbar-start.html'" ng-show="app.navbarstart.show"></ng-include>
<ng-include src="'views/navbar-run.html'" ng-show="app.navbarrun.show"></ng-include>
<!-- jumbotron -->
<ng-include src="'views/jumbotron.html'"></ng-include>
<!-- sayfa başlığı -->
<div class="alert alert-info" ng-show="app.titre.show" translate="{{app.titre.text}}"
translate-values="{{app.titre.model}}"></div>
<!-- sayfa hataları -->
<errors model="app.errors" ng-show="app.errors.show"></errors>
<!-- bekleme mesajı -->
<waiting model="app.waiting" ng-show="app.waiting.show"></waiting>
<!-- geçerli görünüm -->
<ng-view></ng-view>
<!-- hata ayıklama -->
<debug model="app" ng-show="app.debug.on"></debug>
</div>
Görüntülenen görünüm ne olursa olsun, her zaman aşağıdaki öğeleri içerecektir:
- 3-4. satırlar: bir kontrol çubuğu. 3. ve 4. satırlardaki iki çubuk birbirini dışlar;
![]()
![]()
- 6. satır: Uygulamanın logosu / metni:

- 8. satır: bir başlık

- 11. satır: bir hata mesajı:

- 13. satır: bir bekleme mesajı:

- 17. satır: bir hata ayıklama bilgisi:

Yukarıdaki tüm öğeler, [ng-show / ng-hide] yönergesi tarafından kontrol edilir; bu yönerge, öğelerin mevcut olsalar bile mutlaka görünür olmalarını sağlamaz.
3.8.4. Uygulamanın görünümleri
Ana sayfa kodunda şunlar yer almaktadır:
<div class="container">
...
<!-- geçerli görünüm -->
<ng-view></ng-view>
...
</div>
- satır, uygulamanın çeşitli görünümlerini alır. Bunlar, [main.js] modülünde tanımlanmıştır:

Farklı rotaların yapılandırılmasının rolü, 242. sayfadaki 3.7.15.4 numaralı paragrafta açıklanmıştır.
[login.html] görünümü boştur, yani ana sayfada zaten bulunan öğelere yeni bir öğe eklemez.
[home.html] görünümü, ana sayfaya aşağıdaki öğeyi ekler:

[agenda.html] görünümü, ana sayfaya aşağıdaki öğeyi ekler:

[resa.html] görünümü, ana sayfaya aşağıdaki öğeyi ekler:

3.8.5. Uygulamanın Özellikleri
Angular istemcisinin görünümleri, 7. sayfadaki 1.3.3 numaralı paragrafta daha önce tanıtılmıştı. Bu yeni bölümün okunmasını kolaylaştırmak amacıyla, bunları burada tekrar veriyoruz. İlk görünüm şöyledir:
![]() |
- [6], uygulamanın giriş sayfası. Bu, doktorlar için randevu alma uygulamasıdır;
- [7]'te, [debug] modunda olup olmayacağını belirleyen bir onay kutusu bulunur. Bu mod, geçerli görünümün şablonunu görüntüleyen [8] çerçevesinin varlığıyla karakterize edilir;
- [9]'te, milisaniye cinsinden yapay bir bekleme süresi. Varsayılan değeri 0'dır (bekleme yok). N bu bekleme süresinin değeri ise, kullanıcının her eylemi N milisaniyelik bir bekleme süresinden sonra yürütülür. Bu, uygulama tarafından uygulanan bekleme yönetimini görmeyi sağlar;
- [10]'te, Spring 4 sunucusunun URL'i yer alır. Yukarıdakileri takip edersek, bu [http://localhost:8080]'tir;
- [11] ve [12]'te ise uygulamayı kullanmak isteyen kişinin kullanıcı adı ve şifresi yer alır. İki kullanıcı vardır: bir rol (ADMIN) ile admin/admin (kullanıcı adı/şifre) ve bir rol (USER). Yalnızca ADMIN rolüne sahip kullanıcılar uygulamayı kullanma hakkına sahiptir. USER rolü, bu kullanım durumunda sunucunun ne yanıt verdiğini göstermek amacıyla eklenmiştir;
- [13]'te, sunucuya bağlanmayı sağlayan düğme;
- [14]'te, uygulamanın dili. İki seçenek vardır: varsayılan olarak Fransızca ve İngilizce.
![]() |
- [1] ile oturum açılır;
![]() |
- giriş yaptıktan sonra, randevu almak istediğiniz doktoru [2] ve randevu gününü [3] seçebilirsiniz;
- [4] ile seçilen doktorun seçilen gün için randevu takvimini görüntüleyebiliriz;
![]() |
- doktorun randevu takvimini aldıktan sonra, bir randevu saati rezerve edebilirsiniz [5];
![]() |
- [6] komutuyla randevu için hasta seçilir ve bu seçim [7] komutuyla onaylanır;
![]() |
Randevu onaylandıktan sonra, otomatik olarak takvime geri dönülür ve yeni randevu artık takvime kaydedilmiştir. Bu randevu daha sonra [7] ile silinebilir.
Temel işlevler açıklanmıştır. Bunlar basittir. Açıklanmayanlar ise önceki bir görünüme dönmek için kullanılan gezinme işlevleridir. Son olarak dil yönetimine değinelim:
![]() |
- [1]'te Fransızca'dan İngilizce'ye geçilir;
![]() |
- [2]'e, görünüm İngilizceye geçer, takvim de dahil;
3.8.6. [main.js] modülü
[main.js] modülü, uygulamayı kontrol edecek Angular modülünü tanımlar:
![]() |
- 4. satır: modülün adı [rdvmedecins]'tir;
- 5. satır: [ngRoute] modülü, URL modüllerinin yönlendirilmesi için kullanılır;
- 6. satır: [translate] modülü, metinlerin uluslararasılaştırılması için kullanılır;
- 7. satır: [base64] modülü, 'login:password' dizesini Base64 ile kodlamak için kullanılır;
- 8. satır: [ngLocale] modülü, takvimin uluslararasılaştırılması için kullanılır;
- 9. satır: [ui.bootstrap] modülü takvim için kullanılır;
- 12. satır: yol yapılandırması;
- 40. satır: mesajların uluslararası hale getirilmesi;
3.8.7. Ana sayfanın denetleyicisi
[app.html] ana sayfasının HTML kodunu hatırlayalım:
<body ng-controller="appCtrl">
<div class="container">
...
1. satırda, ana sayfanın tüm gövdesi (body) [appCtrl] denetleyicisi tarafından kontrol edilmektedir. Konumu nedeniyle bu denetleyici, uygulamanın genel ve ana denetleyicisidir. 3.7.15 numaralı paragrafta açıklandığı gibi, bu denetleyici tarafından oluşturulan model, ana sayfaya eklenecek tüm görünümler tarafından devralınır.
Kontrolörün kodu şöyledir:
angular.module("rdvmedecins")
.controller("appCtrl", ['$scope', 'config', 'utils', '$location', '$locale',
function ($scope, config, utils, $location, $locale) {
// hata ayıklama
utils.debug("[app] init");
// ----------------------------------------sayfa başlatma
// # sayfalarının şablonları
$scope.app = {waitingTimeBeforeTask: config.waitingTimeBeforeTask};
$scope.login = {};
$scope.home = {};
$scope.agenda = {};
$scope.resa = {};
// geçerli sayfanın şablonu
var app = $scope.app;
...
// ---------------------------------- yöntemler
// geçerli görevi iptal et
app.cancel = function () {
...
};
// çıkış
app.deconnecter = function () {
...
};
// bu kod, önceki [cancel] işlevine atıfta bulunduğu için burada kalmalıdır
app.waiting = {title: {text: config.msgWaitingInit, values: {}}, cancel: app.cancel, show: true};
}])
;
10-14. satırlar, uygulamada kullanılan beş şablonu tanımlamaktadır:
app.html | appCtrl | |
login.html | loginCtrl | |
home.html | homeCtrl | |
resa.html | resaCtrl | |
agenda.html | agendaCtrl |
Anlaşılması gereken önemli nokta, [$scope] nesnesinin ana sayfanın denetleyicisinin şablonu olması ve tüm görünümler ile denetleyiciler tarafından miras alınmasıdır. Dolayısıyla, [loginCtrl] denetleyicisi, [$scope.app, $scope.login, $scope.home, $scope.resa, $scope.agenda] öğelerine erişebilir. Başka bir deyişle, bir denetleyici diğer denetleyicilerin modellerine erişebilir. İncelenen uygulama bu imkânı kullanmaktan özenle kaçınmaktadır. Örneğin, [loginCtrl] denetleyicisi yalnızca iki modelle çalışır:
- kendi modeli olan [$scope.login];
- ve üst denetleyicinin modeli olan [$scope.app];
Aynı durum diğer tüm denetleyiciler için de geçerlidir. [$scope.app] modeli, farklı denetleyiciler arasında paylaşılan bellek olarak kullanılacaktır. Bir C1 denetleyicisinin C2 denetleyicisine bilgi iletmesi gerektiğinde, şu şekilde hareket edilecektir:
[C1]'te:
[C2]'te:
Her iki durumda da, $scope, [appCtrl] denetleyicisinden devralınmıştır ve bu nedenle [C1] ve [C2]'te aynıdır (bu bir işaretçidir). Denetleyiciler arasında paylaşılan bellek görevi gören [$scope.app] nesnesi, klasik web uygulamalarında art arda gelen HTTP istekleri arasında paylaşılan belleği ifade eden oturum terimine benzetilerek, yorumlarda genellikle session olarak anılacaktır.
[appCtrl] denetleyicisinin koduna geri dönelim:
// # sayfalarının şablonları
$scope.app = {waitingTimeBeforeTask: config.waitingTimeBeforeTask};
$scope.login = {};
$scope.home = {};
$scope.agenda = {};
$scope.resa = {};
// geçerli sayfanın şablonu
var app = $scope.app;
// [app.debug] ve [utils.verbose] her zaman senkronize olmalıdır
app.debug = utils.verbose;
app.debug.on = config.debug;
// şu anda sayfa başlığı yok
app.titre = {show: false};
// gezinme çubukları yok
app.navbarrun = {show: false};
app.navbarstart = {show: false};
// hata yok
app.errors = {show: false};
// varsayılan yerel ayar
angular.copy(config.locales['fr'], $locale);
// geçerli görünüm
app.view = {url: undefined, model: {}, done: false};
// geçerli görev
app.task = app.view.model.task = {action: utils.waitForSomeTime(app.waitingTimeBeforeTask), isFinished: false};
- 8. satır: [$scope.app], ana sayfanın şablonu olacaktır. Bu aynı zamanda farklı denetleyiciler arasında paylaşılan bellek olacaktır. Her yere [$scope.app.champ=value] yazmak yerine, [$scope.app] işaretçisi [app] değişkenine atanır ve böylece [app.champ=value] yazılır. Sadece [app]'in ana sayfada gösterilen şablon olduğunu hatırlamak yeterlidir;
- 11. satır: [app.debug.on], uygulamanın debug modunu kontrol eden bir boole değeridir. Varsayılan olarak true değerindedir. Bu değer, gezinme çubuklarındaki [debug] onay kutusuyla bağlantılıdır;
- 15. satır: [app.navbarrun.show], aşağıdaki gezinme çubuğunun görüntülenmesini kontrol eder:
![]()
- 16. satır: [app.navbarstart.show], aşağıdaki gezinme çubuğunun görüntülenmesini kontrol eder:
![]()
- 18. satır: [app.errors], hata başlığının şablonudur;

- 22. satır: [app.view], ana sayfanın [ng-view] etiketiyle şu anda görüntülenen mevcut görünümle ilgili bilgileri içerecektir. Buraya aşağıdaki bilgileri not edeceğiz:
- [url]: geçerli görünümün URL'i, örneğin [/agenda];
- [model]: mevcut görünümün şablonu, örneğin [$scope.agenda];
- [done]: vrai'e göre, mevcut görünümün işini tamamladığını ve başka bir görünüme geçildiğini gösterir;
Bu bilgileri, gezinme kontrolü için kullanılır.
- 24. satır: asenkron bir görev başlatır, simüle edilmiş bir bekleme. Asenkron görev, [app.view.model.task.action] ve [app.task] adlı iki işaretçi ile referanslanır;
[appCtrl] denetleyicisinde iki yöntem ayrıştırılmıştır:
// geçerli görevi iptal et
app.cancel = function () {
...
};
// çıkış
app.deconnecter = function () {
...
};
- 2. satır: [app.cancel] işlevi, o anda bir bekleme mesajının görüntülendiği mevcut görevi iptal etmek için kullanılır. Tüm görünümler bu mesajı gösterir ve bu nedenle görevin iptali burada gerçekleştirilecektir;
- 7. satır: [app.deconnecter] işlevi, kullanıcıyı kimlik doğrulama sayfasına geri yönlendirir. [/login] görünümü hariç tüm görünümler bu seçeneği sunar;
[app.deconnecter] işlevi şöyledir:
// çıkış
app.deconnecter = function () {
// giriş sayfasına geri dönülür
$location.path(config.urlLogin);
};
- 4. satır: URL [/login] oturum açma sayfasına geri dönülür;
3.8.8. Asenkron görev yönetimi
Uygulamamızda, herhangi bir anda yalnızca tek bir asenkron görev yürütülür. Birden fazla görev de olabilir. Örneğin, uygulama başlatıldığında, web hizmetinden önce doktorların listesini, ardından da müşterilerin listesini iki ardışık HTTP isteği ile ister. Aynı işlemi, eşzamanlı iki HTTP isteği ile de gerçekleştirebilirdik. Angular, bu tür bir yönetim için gerekli araçları sunar. Ancak bu örnekte bu seçeneği tercih etmedik.
Çalışmakta olan görev, [appCtrl] denetleyicisinde aşağıdaki kodla iptal edilir:
// mevcut görevi iptal et
app.cancel = function () {
utils.debug("[app] cancel task");
// geçerli görünümün asenkron görevini iptal etme
var task = app.view.model.task;
task.isFinished = true;
task.action.reject();
...
};
- 5. satır: [app.view.model.task] içinde görev aranır. Ayrıca, tüm denetleyiciler asenkron görevlerinin bu nesne tarafından referanslanmasını sağlayacaktır;
- 6. satır: görevin tamamlandığını belirtmek için;
- 7. satır: görevi başarısızlıkla sonlandırmak için. Bu notasyon, incelenen Angular örneklerinde kullanılan notasyondan farklıdır:
- örneklerde, [task] nesnesi, sonlandırılabilen bir [$q.defer()] nesnesiydi;
- nihai sürümde ise [task] nesnesi, [action, isFinished] alanlarına sahip bir nesnedir; burada [action],ve [isFinished] ise eylemin tamamlandığını belirten bir boole değeridir;
Bir örnek üzerinden [task] nesnesinin yaşam döngüsünü inceleyelim. Başlangıçta, [appCtrl] denetleyicisinden sonra, [loginCtrl] denetleyicisi devreye girerek [views/login.html] görünümünü görüntüler. Bu denetleyicinin başlatma kodu şöyledir:
// üst modeli alınır
var login = $scope.login;
var app = $scope.app;
// geçerli görünüm
app.view = {url: config.urlLogin, model: login, done: false};
5. satırda [model=login] yer almaktadır. Bu, [login] nesnesini değiştirdiğimizde, [app.view.model] nesnesini ve dolayısıyla [$scope.app.view.model] nesnesini de değiştirdiğimiz anlamına gelir. [loginCtrl] denetleyicisinde simüle edilmiş bir bekleme yapmak istediğimizde şunu yazarız:
// simüle edilmiş bekleme
var task = login.task = {action: utils.waitForSomeTime(app.waitingTimeBeforeTask), isFinished: false};
[task] alanı, [login] nesnesine eklendiğinde, aslında [$scope.app.view.model] nesnesine eklenmiş olur. Kullanıcı beklemeyi iptal ederse, [appCtrl.cancel]'teki kod:
// geçerli sayfanın modeli
var app = $scope.app;
...
var task = app.view.model.task;
task.isFinished = true;
task.action.reject();
simüle edilen beklemeyi başarıyla sonlandıracaktır (4-6. satırlar).
3.8.9. Gezinti Denetimi
Uygulamada kullanılan navigasyon kuralları şunlardır:
herhangi bir | evet | |
/login | evet, eğer denetçi [loginCtrl] işini bitirdiğini bildirmişse | |
/home | evet | |
/agenda | evet | |
/ana sayfa | evet, eğer denetçi [homeCtrl] işini bitirdiğini bildirmişse | |
/resa | evet | |
/agenda | evet | |
/takvim | evet, eğer denetçi [homeCtrl] işini bitirdiğini belirtmişse | |
/resa | evet |
Bu, aşağıdaki kodla uygulanır:
[agendaCtrl] için:

- 11-20. satırlar: gezinme kuralının uygulanması;
- 26. satır: yeni geçerli görünüm;
[resaCtrl] için:

- satır 12-20: gezinme kuralının uygulanması:
- 27. satır: yeni geçerli görünüm;
[loginCtrl] için:

- burada herhangi bir gezinme denetimi yoktur, çünkü kural, URL [/login] sayfasına herhangi bir yerden gelinebileceğini belirtir. Dolayısıyla, kullanıcı tarayıcısına bu URL adresini yazarsa, o anda geçerli olan görünüm ne olursa olsun bu işlem çalışacaktır;
- 16. satır: yeni geçerli görünüm;
[homeCtrl] denetleyicisi için kod, 3.8.7. paragrafında verilmiştir.
Son olarak, şu kural için:
/home | evet, eğer [homeCtrl] denetleyicisi işini bitirdiğini bildirmişse |
URL [/home]'ten URL [/agenda]'e geçişi sağlayan bir kod örneği şöyledir:
![]() |
Yukarıda, [homeCtrl] denetleyicisinin [afficherAgenda] yöntemindeyiz. Kullanıcı bir doktorun randevu takvimini talep etti.
- 107. satır: HTTP görevinin taahhüdü;
- 109. satır: [app] değişkeni, [$scope.app] ile başlatılmıştır. Daha önce gördüğümüz gibi, bu son nesne [app.html] görünümünün şablonu olarak kullanılmaktadır. Bu [$scope.app] şablonu, görünümler arasında paylaşılması gereken bilgileri depolamak için de kullanılır;
- 111. satır: görev tarafından döndürülen hata kodu analiz edilir;
- 113. satır: [result.data] sonucu, [app] şablonuna yerleştirilir;
- 116. satır: [homeCtrl] denetleyicisi, kontrolü [agendaCtrl] denetleyicisine devredecektir. 115. satırdaki kodla işini tamamladığını ona bildirir. Bu kod, [agendaCtrl] denetleyicisi tarafından şu şekilde kullanılacaktır:

- 11. satır: [$scope.app.view] nesnesi alınır;
- 15. satır: [homeCtrl] tarafından başlatılan [$scope.app.view.done] alanı işlenir;
3.8.10. Hizmetler
![]() |
[config, utils, dao] hizmetleri, Angular sunumunda daha önce açıklanan hizmetlerdir:
- [config] hizmeti 3.7.4 numaralı paragrafta tanıtılmıştır;
- [utils] hizmeti 3.7.5. paragrafta tanıtılmıştır;
- [dao] hizmeti 3.7.6. paragrafta tanıtılmıştır;
Hatırlatmak gerekirse, bu hizmetlerin yapısı şöyledir:
[config] hizmeti
![]() |
- [1]'te: kodun yaklaşık 250 satırdan oluştuğu görülmektedir. Bu kodun büyük bir kısmı, [2] uluslararasılaştırılmış mesajların anahtarlarının dışsallaştırılmasından oluşmaktadır. Bu anahtarların koda sabit olarak yazılması önlenmektedir;
[utils] Hizmeti
![]() |
- 8. satır: [verbose] değişkeniyle henüz karşılaşmamıştık. Bu değişken, [debug] işlevini şu şekilde kontrol eder:
![]() |
- 22-25. satırlar: [utils.debug] işlevi, [verbose.on]'in değeri false olarak değerlendirilirse hiçbir işlem yapmaz. Bu değişken, [appCtrl] denetleyicisindeki bir değişkenle bağlantılıdır:
![]() |
- 21. satır: [app.debug], [utils.verbose] işaretçisinin değerini alır. Dolayısıyla, [app.debug] üzerinde yapılan her değişiklik, [utils.verbose] üzerinde de yapılacaktır;
- 22. satır: [app.debug.on]'in başlangıç değeri yapılandırma dosyasından alınır. Varsayılan olarak bu değer true.'tir. Bu değer zamanla değişebilir. Kullanıcı, gezinme çubuklarından bu değeri değiştirebilir:
![]() |
- 45. satır: Bir onay kutusu (type=checkbox) sayesinde [app.debug.on] değerini (ng-model özniteliği) değiştirebilirsiniz;
[dao] Hizmeti
![]() |
3.8.11. Yönergeler
![]() |
[errors, footable, list, waiting] yönergeleri, Angular'ın tanıtımı sırasında daha önce açıklananlardır:
- [footable] yönergesi 3.7.8.6 numaralı paragrafta tanıtılmıştır;
- [list] yönergesi 3.7.12. paragrafta tanıtılmıştır;
- [errors] ve [waiting] direktifleri 3.7.14. paragrafta tanıtılmıştır;
[debug] yönergesiyle daha önce karşılaşmamıştık. Bu yönerge şöyledir:
![]() |
11. satırda atıfta bulunulan [debug.html] dosyası şöyledir:
- 2. satır: [debug] yönergesi, şablonunu JSON biçiminde bir Bootstrap başlık çubuğunda (1. satır) görüntüler;
Bu yönerge yalnızca [app.html] ana sayfasında kullanılır:
![]() |
- [debug] yönergesi 35. satırda kullanılır. Dolayısıyla, hata ayıklama modundayken (ng-show özniteliği) [$scope.app] şablonunun JSON biçimini görüntüler. Bu da şu şekilde bir sonuç verir:
![]() |
Bunun yorumlanabilmesi için kod hakkında iyi bir bilgi birikimi gereklidir; ancak bu bilgi birikimi elde edildiğinde, yukarıdaki bilgiler hata ayıklama için yararlı hale gelir. Burada, görüntülenen [$scope.app] modelinin öğeleri vurgulanmıştır. [$scope.app]'in denetleyiciler tarafından paylaşılan bellek olduğunu hatırlatırız;
- [waitingBeforeTask]: Herhangi bir HTTP isteğinden önce simüle edilen bekleme süresi;
- [debug]: hata ayıklama modu – bu başlık çubuğu görüntüleniyorsa, bu değer mutlaka true olur;
- [navbarrun]: Aşağıdaki gezinme çubuğunun görüntülenmesini kontrol eden boole değeri:
![]()
- [navbarstart]: Aşağıdaki gezinme çubuğunun görüntülenmesini kontrol eden boole değeri:
![]()
- [errors]: [errors] yönergesinin şablonu;
- [view]: şu anda görüntülenen görünümle ilgili bilgileri içerir;
- [waiting]: [waiting] yönergesinin şablonu;
- [serverUrl, username, password]: web hizmetine bağlantı bilgileri;
- [medecins]: doktorlara uygulanan [list] yönergesi için şablon;
- [clients]: müşteriler için de aynı;
- [menu]: Görüntülenen menü seçeneklerini kontrol eder. Bu seçenekler [navbar-run.html]'te tanımlanmıştır:

Menü seçenekleri 16, 23, 29 ve 36. satırlarda yer almaktadır.
- [formattedJour]: takvimde 'yyyy-aa-gg' biçiminde seçilen gün;
- [agenda]: doktorun randevu takvimi. Bu takvimde boş (rv==null) ve rezerve edilmiş zaman aralıkları bulunur. Rezerve edilmiş zaman aralıklarında, rezervasyonu yapan müşterinin adı yer alır;
- [selectedCreneau]: Rezervasyon yapmak için seçilen zaman aralığı;
3.8.12. [loginCtrl] denetleyicisi
![]() |
[loginCtrl] denetleyicisi, [views/login.html] görünümüne bağlıdır ve bu görünüm, ana sayfayla birleştirildiğinde aşağıdaki sayfayı oluşturur:

[loginCtrl] denetleyicisi şöyledir:

- 13. satır: [login], geçerli görünümün şablonu olacaktır;
- 14. satır: [app], denetleyiciler arasında paylaşılan bellektir;
- 16. satır: [app.view]'e geçerli görünümün bilgileri girilir;
Bu başlatma kodu her denetleyicide bulunacaktır. M1 modeline sahip V1 görünümünün C1 denetleyicisi için aşağıdaki başlatma kodu olacaktır:
- 18. satır: Hatırlanacağı üzere, [appCtrl], [app.task.action] nesnesi tarafından referans verilen simüle edilmiş bir bekleme başlatmıştı. Bu görevin [promise] nesnesi, görevin tamamlanmasını beklemek için kullanılır;
- 39. satır: [login.setLang] yöntemi dil değişikliğini yönetir;
- 47. satır: [login.authenticate] yöntemi, kullanıcı kimlik doğrulamasını yönetir;
Kimlik doğrulama yöntemindeki ana adımlara bir göz atalım:

- 50-51. satırlar: [app.waiting], bekleme çubuğunun şablonudur;
- 53. satır: [app.errors], hata bildirim şeridinin şablonudur;
- 55. satır: Simüle edilmiş bir bekleme başlatılır. [action, isFinished] nesnesi, [login.task] tarafından referans alınır ve dolayısıyla [app.view.model=login], [app.view.model.task] tarafından referans alınır. Görevin iptal edilebilmesi için bu koşulun gerekli olduğunu hatırlatırız;
- 57. satır: Simüle edilen bekleme süresi sona erdikten sonra doktorlar yüklenir;
- 62. satır: Doktor talepleri alındığında, bu talepler analiz edilir. Doktorlar bulunmuşsa, bu durumda müşteriler talep edilir;
- 83. satır: Alınan yanıt analiz edilir ve son görünüm görüntülenir. Bu, aşağıdaki kodla gerçekleştirilir:

- satır 87: Aşağıdaki durumlarda [task.isFinished] boole değeri, true olarak ayarlanır:
- kullanıcı beklemeyi iptal ettiyse;
- doktorların talebi bir hata ile sonuçlandı;
- 91-98. satırlar: müşterilerin olduğu durum;
- satır 93: [app.clients], müşterileri bir açılır listede gösterecek olan [list] yönergesinin şablonudur;
- 97-98. satırlar: görünüm değiştirmeye hazırlanıyoruz (98. satır) ancak öncesinde denetleyicinin işini tamamladığını belirtiyoruz (97. satır). [$scope.app.view.done]'in gezinme denetimi için kullanıldığını hatırlatırız;
Burada dikkat edilmesi gereken önemli nokta, doktorların ve müşterilerin tarayıcıda önbelleğe alınmış olmasıdır. Artık web hizmetinden bu veriler talep edilmeyecektir.
3.8.13. [homeCtrl] denetleyicisi
![]() |
[homeCtrl] denetleyicisi, ana sayfayla birleştirildiğinde aşağıdaki sayfayı oluşturan [views/home.html] görünümüyle ilişkilidir:

[homeCtrl] denetleyicisinin yapısı şöyledir:

- 12-20. satırlar: bu, gezinme denetimidir. [loginCtrl] hariç tüm denetleyicilerde bu denetim bulunur, çünkü [/login.html] sayfasına koşulsuz olarak erişilebilir;

- 25-28. satırlar: Burada, [loginCtrl] denetleyicisinde karşılaştığımız satırlara benzer satırlar bulunmaktadır. Dolayısıyla [home], denetleyiciyle ilişkili görünümün şablonudur;
- 33. satır: Daha önce karşılaşmadığımız bir öznitelik. Bu, görünümün başlık çubuğunun şablonudur:
![]()
- 36. satır: [home.datepicker], takvimin şablonudur;
- 38. satır: [app.menu], gezinme çubuğu menüsünün şablonudur. Burada [Agenda] seçeneği yer alacaktır. Bir doktorun randevu takvimini sorgulamayı sağlayan seçenek budur;
Son olarak, denetleyicinin iki yöntemi vardır:

Randevu takviminin görüntülenmesi (satır 51), 3.7.8. paragrafında ele alınmıştır.
3.8.14. [agendaCtrl] denetleyicisi
![]() |
[agendaCtrl] denetleyicisi, ana sayfayla birleştirildiğinde aşağıdaki sayfayı oluşturan [views/agenda.html] görünümüne bağlıdır:

[agendaCtrl] denetleyicisinin yapısı şöyledir:

- 10-20. satırlar gezinme kontrolünü sağlar;

- 23-26. satırlar: [agenda], [agendaCtrl] denetleyicisiyle ilişkili görünümün şablonu olacaktır;
- 36-44. satırlar: [app.titre], aşağıdaki başlık çubuğunun şablonudur:

- 46. satır: Menüde [Home / Accueil] seçeneği yer alacaktır:
![]()
Denetleyicinin yöntemleri şunlardır:

- 95. satır: [agenda.supprimer] yöntemi 3.7.9. paragrafta ele alınmıştır;
[agenda.home] yöntemi, tamamen gezinme amaçlı bir yöntemdir:

[agenda.reserver] yöntemi şöyledir:

- 73. satır: [reserver] işlevinin parametresi, zaman diliminin numarasıdır (id);
- 77-86. satırlar: bu kimliğe sahip zaman aralığını bulmaya yöneliktir;
- satır 82: Bulunan zaman aralığı, [app] paylaşımlı belleğine kaydedilir. Kontrolü devralacak olan (satır 90) [resaCtrl] denetleyicisi, bu bilgiyi kullanarak başlık çubuğunu görüntüleyecektir;
- satır 89-90: [/resa.html]'e geçiş yapılır;
3.8.15. [resaCtrl] denetleyicisi
![]() |
[resaCtrl] denetleyicisi, ana sayfayla birleştirildiğinde aşağıdaki sayfayı oluşturan [views/resa.html] görünümüne bağlıdır:

[resaCtrl] denetleyicisinin yapısı şöyledir:

- 12-20. satırlar: gezinme denetimi;

- 24-27. satırlar: [resa], geçerli görünümün şablonu olacaktır;
- 38-45. satırlar: [app.titre], aşağıdaki başlık çubuğunun şablonudur:

- 47. satır: iki menü seçeneği görüntülenir:
![]()
Denetleyicinin yöntemleri şunlardır:

[resa.valider] yöntemi 3.7.9. paragrafında incelenmiştir.
3.8.16. Dil yönetimi
Tüm denetleyiciler aşağıdaki [setLang] yöntemini sunar:

Bu yöntem, [appCtrl] denetleyicisinde faktörlere ayrılabilirdi.




























































































































