HTMX Felsefesi ve Temel İlkeleri
HTMX teknolojisi, HTML'in temel yapı taşlarından biridir ve
HTML'in temel
işlevlerinden biridir.
Bu bölümde, HTMX hakkında felsefi ve tarihsel açıklamalar verilecektir.
HTMX Sunucu Gereksinimi
HTMX'in çalışması için bir sunucu ortamı gereklidir
HTMX, sunucu tarafı (server-side) bir teknolojidir ve HTTP istekleri göndermek için çalışma zamanında bir sunucuya ihtiyaç duyar.
Bu sayfadaki kod örneklerini test etmek için aşağıdaki seçeneklerden birini kullanabilirsiniz:
- Gerçek Sunucu: Python Flask/Django, Node.js Express, PHP, Ruby on Rails gibi bir backend framework kullanarak bir sunucu ortamı oluşturabilirsiniz.
- Mock Script: Geliştirme aşamasında, tarayıcı konsolunda çalışan basit bir JavaScript mock script'i kullanarak HTMX isteklerini simüle edebilirsiniz.
-
Live Server: VS Code'un
Live Serveruzantısı veya benzeri bir araç, statik dosyalar için yeterli olabilir, ancak dinamik içerik için gerçek bir backend gereklidir.
Web'in Kayıp Kökenlerine Dönüş: HTMX Felsefesi Felsefi ve Tarihsel Açıklama
HTMX, teknik olarak yeni bir kütüphane gibi görünse de, felsefi olarak web'in babası Roy Fielding'in 2000 yılında doktora tezinde (REST mimarisi) ortaya koyduğu temel ilkelere bir dönüş hareketidir.
Modern web geliştirmede (React, Vue, Angular gibi framework'lerde) sıkça kullanılan "Single Page Application" (SPA) mantığı, sunucudan sadece JSON verisi alıp bunu tarayıcıda işler.
Ancak bu durum, HTML'in orijinal gücünü bir kenara iter.
HATEOAS ve Web’in Orijinal Vizyonuna DönüşRoy Fielding’in 2000 yılında yayınladığı doktora tezi, bugün bildiğimiz internetin mimari omurgası olan REST (Representational State Transfer) kavramını tanımlıyordu.
Ancak modern web geliştiriciliği, bu tezin en kritik bileşenlerinden birini zamanla unuttu:HATEOAS (Hypermedia As The Engine Of Application State).
Günümüzün popüler Single Page Application (SPA) yaklaşımları, sunucuyu sadece ham veri (JSON) sağlayan pasif bir depoya dönüştürürken, uygulamanın tüm mantığını ve durum yönetimini tarayıcıdaki karmaşık JavaScript kodlarına yıktı.
Bu durum, web'in doğasına aykırı bir karmaşıklık yarattı.
HATEOAS İlkesi Savunması:İstemci (tarayıcı), sunucuyla etkileşime girerken uygulamanın o anki durumunu ve bir sonraki olası adımlarını, sunucudan dönen Hypermedia (HTML) sayesinde bilmelidir.
Tıpkı bir web sayfasında gezinirken bir sonraki sayfaya gitmek için sadece bir linke tıklamanız ve o linkin sizi nereye götüreceğini bilmeniz gibi; API’ler ve uygulamalar da bu akışkanlıkta olmalıdır.
Ancak HTML standardı uzun yıllar boyunca bu vizyonu tam olarak destekleyemedi.
HTMX’in yaratıcısı Carson Gross, HTML’in gelişiminin durakladığı noktada şu kritik tespiti yaptı: HTML spesifikasyonunda yalnızca <a> (linkler) ve <form> etiketleri sunucuya istek gönderme yeteneğine sahipti.
Oysa bir <div>, bir <button> veya klavye girdileri de bu yeteneğe sahip olmalıydı.
HTMX, HTML'i bir "metin formatı" olmaktan çıkarıp, Fielding’in hayal ettiği gibi tam donanımlı bir "Hypertext Transfer" aracına dönüştürür.
Bu sayede geliştirici, JSON verilerini işleyip DOM'u güncellemek için ekstra bir JavaScript katmanı yazmak zorunda kalmadan, sunucunun doğrudan nihai arayüzü göndermesini sağlar.
Bu, tekerleği yeniden icat etmek değil; tekerleğin olması gerektiği gibi dönmesini sağlamaktır.
Locality of Behavior (LoB): Kodun Bilişsel Yükünü AzaltmakYazılım mühendisliğinde yıllardır dogmatik bir şekilde savunulan "Separation of Concerns" ( İlgi Alanlarının Ayrılması ) ilkesi, web geliştirmede genellikle yanlış yorumlanmıştır.
Geliştiriciler, "ilgi alanlarını" ayırmak yerine "teknolojileri" ayırdılar:
HTML'i yapı, CSS'i stil, JavaScript'i ise davranış için farklı dosyalara böldüler.
Kağıt üzerinde temiz görünen bu yaklaşım, pratikte kodun okunabilirliğini ve bakımını zorlaştıran bir duruma yol açtı.
Bir butona baktığınızda ne işe yaradığını anlamak için proje klasörlerinde o butona bağlı "event listener" kodunu aramak zorunda kalmanız, sektörde "Spooky Action at a Distance" (Uzaktan Gizemli Eylem) olarak adlandırılan bir kopukluk yaratır.
HTMX, bu dağınıklığa Locality of Behavior (LoB) - Davranışın Yerelliği ilkesiyle cevap verir.
LoB İlkesi Savunması:"Bir kod biriminin davranışı, tanımlandığı yerde açıkça belli olmalıdır."
Bir HTML elementi ne yapıyorsa, bu bilgi o elementin üzerinde yer almalıdır.
Bu yaklaşım, kodun "bütünlüğünü" (Cohesion) artırır.
Bu felsefenin en büyük avantajı bakım kolaylığıdır.
Klasik yöntemde HTML'den bir butonu sildiğinizde, JavaScript dosyasında o butona ait öksüz kalmış kodlar (event handler'lar) birikmeye başlar.
Ancak LoB yaklaşımında, HTMX öznitelikleriyle donatılmış bir HTML elementini sildiğinizde, onunla ilişkili davranışı da silmiş olursunuz.
Bu, geliştiricinin zihinsel yükünü hafifletir, kodun ne yaptığını anlamak için dosyalar arasında kaybolmasını engeller ve "JavaScript Yorgunluğu" (Javascript Fatigue) dediğimiz tükenmişliğin önüne geçer.
HTMX ile kod yazmak, karmaşık bir durum yönetimi bulmacası çözmek değil, deklaratif ve öngörülebilir bir süreç haline gelir.
HTMX Konusu Detaylı İçerik ve Örnekleri
HTMX, HTML üzerinden JavaScript kullanarak dinamik web sayfaları
oluşturmayı sağlayan bir
teknolojidir.
Bu bölümde, HTMX hakkında detaylı içerik ve örnekler
verilecektir.
HTMX Yapısına Giriş Temel Kavramlar
HTMX'in teknik altyapısı, geleneksel JavaScript kütüphanelerinden farklı olarak "Deklaratif Programlama" modeline dayanır.
Yani geliştirici olarak siz, tarayıcıya bir işin nasıl yapılacağını (adım adım JavaScript kodu yazarak) anlatmazsınız; bunun yerine HTML etiketlerine eklediğiniz özel niteliklerle (attributes) ne yapılmasını istediğinizi söylersiniz.
Tüm karmaşık AJAX süreçleri, olay dinleyicileri (event listeners) ve DOM manipülasyonları arka planda HTMX tarafından otomatik yönetilir.
Bu yapının kalbinde "HTML Over The Wire" (Kablo Üzerinden HTML) prensibi yatar.
Modern Single Page Application (SPA) yapılarında sunucu tarayıcıya ham veri (JSON) gönderir ve tarayıcı bu veriyi işleyip HTML'e çevirmek için yoğun bir efor sarf eder.
HTMX modelinde ise sunucu, doğrudan kullanıma hazır HTML parçaları (HTML Fragments) gönderir.
Tarayıcı bu yanıtı alır ve sayfada belirlediğiniz hedef bölgeye ( Target ) doğrudan yerleştirir ( Swap ).
Bu yöntem, "Virtual DOM" gibi karmaşık ara katmanlara ihtiyaç duymadan yüksek performanslı ve akıcı arayüzler oluşturmanızı sağlar.
HTMX Nedir Tanımı ve Özellikleri Metaforlar İle Açıklama
HTMX, modern web tarayıcılarının yeteneklerini doğrudan HTML üzerinden kullanmanıza olanak tanıyan, yaklaşık 14kb boyutunda, yüksek performanslı bir kütüphanedir.
Geleneksel web geliştirmede AJAX istekleri, CSS geçişleri (transitions), WebSockets ve Server Sent Events (SSE) gibi dinamik işlemler için yoğun JavaScript kodu yazmanız gerekir.
HTMX, bu özellikleri doğrudan HTML etiketlerine (attributes) taşıyarak, JavaScript yazmadan modern ve hızlı kullanıcı arayüzleri oluşturmanızı sağlar.
HTMX bir "framework" (React, Vue gibi) değil, HTML'in eksik bırakılan yeteneklerini tamamlayan bir eklentidir.
Amacı, geliştiricinin odağını karmaşık durum yönetiminden (state management) alıp, web'in temel yapı taşı olan HTML'e geri vermektir.
Gerçek Hayattan Benzetmelerle HTMX MantığıTeknik tanımların ötesinde, HTMX'in ne yaptığını ve neden farklı olduğunu şu senaryolarla zihnimizde daha net canlandırabiliriz:
Tercüman" Metaforu (JavaScript Bağımlılığı)Standart HTML'i, yabancı bir ülkede sadece iki kelime bilen bir turist gibi düşünebilirsiniz: Sadece "Git" (<a>) ve "Gönder" (<form>) diyebilir.
Eğer bu turist "Bunu sil", "Şunu güncelle" veya "Canlı ara" demek isterse, yanında sürekli bir tercüman ( JavaScript ) dolaştırmak zorundadır.
Tercüman isteği dinler, çevirir, işi halleder.
HTMX ise bu turiste dil öğretir. Artık turistin bir tercümana ihtiyacı yoktur. Doğrudan "Bunu sil" veya "Bunu değiştir" diyebilir.
Aradaki aracı kalktığı için iletişim daha doğrudan ve hızlıdır.
"Ev Tadilatı" Metaforu (Sayfa Yenileme vs. Swap)Geleneksel bir web sayfasında (HTMX olmadan), salondaki bir ampulü değiştirmek istediğinizde, müteahhidin tüm evi yıkıp temellerden başlayarak aynısını yeniden inşa etmesi ve en son yeni ampulü takması gibidir (Tam Sayfa Yenileme).
Bu, büyük bir kaynak israfıdır. HTMX ile yaklaşım şudur: Sadece ampulü sökersiniz ve yenisini takarsınız.
Evin geri kalanı (Header, Footer, Sidebar) olduğu yerde durur. Duvarlara dokunulmaz, sadece değişmesi gereken parça değişir.
"Demonte Mobilya" vs. "Hazır Mobilya" (JSON vs. HTML)Modern Frontend Framework'leri, sunucudan genellikle JSON verisi alır.
Bunu, kargoyla size gelen demonte (parçalanmış) mobilyaya benzetebiliriz.
Kutudan sadece tahtalar ve vidalar çıkar ( Veri ), ama oturabilmek için evde sizin saatlerce montaj yapmanız ve uğraşmanız ( Client-side Rendering ) gerekir.
HTMX ise "Hazır Mobilya" prensibidir.
Sunucu size vida ve tahta göndermez; doğrudan oturmaya hazır, montajı yapılmış bir koltuk ( HTML Parçası ) gönderir.
Paketi açar ve odanın köşesine koyarsınız. Ekstra bir montaj eforuna gerek kalmaz.
HTMX Kurulum ve Web’in Eylemleri HTTP Metotları
HTMX'i projenize dahil etmek için npm, webpack veya karmaşık derleme (build) araçlarına ihtiyacınız yoktur.
Kütüphanenin felsefesi sadelik üzerine kuruludur; bu nedenle tek bir JavaScript dosyası ekleyerek anında kullanmaya başlayabilirsiniz.
Bu sadelik, özellikle öğrenme aşamasında odağın "konfigürasyon" yerine "kodlama" üzerinde kalmasını sağlar.
Web dünyasında sunucu ile iletişim kurarken kullanılan dört temel eylem (HTTP Verbs) vardır.
Standart HTML maalesef bu
eylemlerin sadece ikisini (
HTMX ise bu kısıtlamayı kaldırarak modern REST mimarisinin dört temel ayağını da kullanmanıza olanak tanır:
Web’in Dört Temel Eylemi (HTTP Metotları)Web tarayıcınız ve sunucu arasındaki konuşma, rastgele kelimelerle değil, standartlaştırılmış fiillerle (Verbs) gerçekleşir.
HTMX, HTML'in yapay kısıtlamalarını kaldırarak bu fiillerin tamamını kullanmanıza olanak tanır.
GET: Bilgi Alma ve GörüntülemeBu metod, "bana veriyi göster" demektir ve en güvenli istektir.
Sunucudaki hiçbir şeyi değiştirmez, silmez veya yeni bir kayıt oluşturmaz; sadece var olanı okur.
Mantığı:Bir kütüphaneden kitap alıp okumak gibidir.
Kitabın yeri veya içeriği değişmez, sadece bilgi size ulaşır.
HTMX Kullanımı:Genellikle sayfa yüklendiğinde bir listeyi getirmek, sekmeler (tabs) arasında geçiş yapmak, arama sonuçlarını listelemek veya bazı buton türleri "daha fazla yükle" için kullanılır.
POST: Yeni Kayıt OluşturmaSunucuya "bu veriyi al ve kaydet" demektir.
Genellikle veritabanında daha önce var olmayan, yeni bir girdiyi oluşturmak için kullanılır.
Mantığı:Posta kutusuna bir mektup atmak gibidir. Mektubu attıktan sonra süreç başlar ve karşı tarafta yeni bir bilgi oluşur.
HTMX Kullanımı:Yeni bir kullanıcı kaydı oluşturmak, bir blog yazısına yorum göndermek veya alışveriş sepetine yeni bir ürün eklemek gibi eylemlerde kullanılır.
PUT: Güncelleme ve DüzenlemeVar olan bir verinin üzerine yazmak veya onu değiştirmek için kullanılır.
Standart HTML formları (<form>) bu metodu desteklemez, ancak modern web mimarisi (REST) için kritiktir.
HTMX bu eksikliği giderir.
Mantığı:Yazılmış bir defter sayfasındaki hatalı bir cümleyi silgiyle silip doğrusunu yazmak gibidir.
Sayfa (kayıt) oradadır, sadece içeriği değişir.
HTMX Kullanımı:Profil ayarlarında isminizi değiştirmek, bir görev listesindeki (To-Do) maddenin durumunu "tamamlandı" olarak işaretlemek veya bir ürünün stok adedini güncellemek için kullanılır.
DELETE: Veriyi Yok EtmeSunucudaki bir kaynağın kalıcı olarak kaldırılması talimatıdır.
Tıpkı PUT gibi, standart HTML formları bunu doğrudan desteklemez.
HTMX sayesinde herhangi bir butona bu yeteneği verebilirsiniz.
Mantığı:Bir belgeyi kağıt öğütücüye atmak gibidir.
İşlem tamamlandığında o veri artık erişilemez olur.
HTMX Kullanımı:Bir yorumu silmek, abonelikten çıkmak, alışveriş sepetinden bir ürünü çıkartmak veya bir bildirim kartını ekrandan (ve sunucudan) kaldırmak için kullanılır.
HTMX’in Beyni Operasyonel Öznitelikler
Bir önceki bölümde, HTML elementlerine konuşma yeteneği ( HTTP Metotları ) kazandırdık.
Ancak etkili bir iletişim, sadece konuşmaktan ibaret değildir; cevabın nerede karşılanacağı, nasıl işleneceği ve bu diyaloğun tam olarak hangi şartlarda başlayacağı da hayati önem taşır.
Geleneksel JavaScript geliştirmesinde, bir sunucu yanıtını ekrana basmak için DOM elementlerini elle seçmeniz ( query selector ), içeriği temizlemeniz ve yeni veriyi enjekte etmeniz gerekir.
HTMX, bu karmaşık manuel operasyonların tamamını "deklaratif" (bildirimsel) bir yapıya dönüştürür.
Artık kod yazarak "Elementi bul, veriyi al, içine yaz" demek yerine; HTML etiketi üzerine "Cevabı şuraya koy" diyerek niyetimizi belli ederiz.
İşte HTMX'in çalışma mantığını yöneten ve kütüphanenin "beyni" olarak nitelendirebileceğimiz üç temel mekanizma (silahşör) burada devreye girer:
Zamanlama (Trigger): Olayın KıvılcımıBir web sayfasında etkileşim sadece "tıklamak"tan ibaret değildir. hx-trigger, sunucu ile iletişimin ne zaman başlayacağını belirleyen kurallar bütünüdür.
Standart HTML'de bir bağlantıya tıklandığında süreç hemen başlar; ancak HTMX burada geliştiriciye zamanı bükme yeteneği verir.
Sadece kullanıcının aktif hareketleri (tıklama, tuşa basma, mouse ile üzerine gelme) değil; pasif durumlar da bir tetikleyici olabilir.
Örneğin:Bir elementin ekranda görünür olması (scroll yaparken), belirli bir sürenin dolması veya başka bir elementin değişmesi sunucuya istek göndermek için yeterlidir.
Ayrıca, "kullanıcı yazmayı bitirene kadar bekle" (delay) gibi değiştiricilerle, sunucuyu gereksiz trafikten koruyan akıllı bekleme mekanizmaları da burada tanımlanır.
Kısacası Trigger, iletişimi başlatan işaret fişeğidir.
Konumlandırma (Target): Hedef Şaşırtma SanatıVarsayılan olarak, bir element sunucudan cevap istediğinde, gelen yanıt o elementin kendi içine yerleşir.
Ancak modern arayüz tasarımlarında genellikle eylem bir yerde, değişim başka bir yerde olur.
hx-target mekanizması, eylemi yapan ile etkilenen elementi birbirinden ayırır.
Bunu bir televizyon kumandası gibi düşünebilirsiniz: Tuşa kumanda üzerinde basarsınız (Trigger), ama görüntü televizyonda değişir (Target).
HTMX'te bir butona tıkladığınızda butonun kendisinin kaybolmasını değil, yan taraftaki bir mesaj kutusunun dolmasını veya bir listenin güncellenmesini isteyebilirsiniz.
Bu öznitelik sayesinde, sayfanın herhangi bir yerindeki bir etkileşimle, sayfanın bambaşka bir köşesindeki içeriği, sayfa yenilenmeden güncelleyebilirsiniz.
Yerleştirme Stratejisi (Swap): Cerrahi MüdahaleHedef belirlendi ve sunucudan yeni HTML paketi geldi.
Peki bu yeni parça sayfaya nasıl monte edilecek?
hx-swap, bu entegrasyonun stratejisini belirler.
Bu sadece "eskiyi sil, yeniyi koy"dan ibaret değildir; çok daha hassas seçenekler sunar.
Gelen içeriği mevcut kutunun içine mi koyacağız (innerHTML), yoksa kutuyu tamamen söküp atarak yerine yenisini mi inşa edeceğiz (outerHTML)?
Belki de mevcut içeriğe dokunmadan, listenin en sonuna yeni bir satır eklemek (beforeend) veya en başına yeni bir bildirim tutturmak (afterbegin) istiyoruzdur.
Swap mekanizması, sayfadaki DOM yapısını bozmadan, sadece gerekli olan milimetrik değişikliği yapmanızı sağlayan cerrahi bir yöntemdir.
Bu sayede sayfa titremez, scroll pozisyonu bozulmaz ve kullanıcı akıcı bir deneyim yaşar.
HTMX Öznitelik Mimarisi HTML'in Sınırlarını Zorlamak
HTMX'in temel büyüsü, HTML etiketleri (tag) üzerine eklenen ve hx- ön ekiyle başlayan özel özniteliklerde saklıdır.
Bu öznitelikler, harici JavaScript dosyalarına veya karmaşık framework yapılarına ihtiyaç duymadan, standart HTML elementlerine doğrudan "davranış" (behavior) kazandırmanızı sağlar.
Web'in ilk günlerinden bu yana HTML standardı, sunucu ile iletişim kurma yeteneğini yalnızca iki elemente bahşetmişti: Bir sayfaya gitmek için Linkler (<a>) ve veri göndermek için Formlar (<form>).
Diğer tüm elementler (div, button, input, section vb.) dilsiz ve pasifti; sunucuyla konuşamazlardı.
HTMX öznitelikleri, bu tarihsel kısıtlamayı ortadan kaldırarak HTML'i genelleştirir ve her elemente sunucu ile iletişim kurma özgürlüğü tanır.
Deklaratif (Bildirimsel) Programlama Yaklaşımı HTMX öznitelikleri, "İşlem Odaklı" ( Imperative ) değil, "Sonuç Odaklı" ( Declarative ) bir yaklaşım sunar.
Geleneksel JavaScript geliştirmesinde, tarayıcıya nasıl yapacağını adım adım anlatırsınız (Önce veriyi çek, sonra JSON'u ayrıştır, sonra DOM'u bul, sonra içeriği temizle, sonra yenisini ekle...). HTMX ile ise sadece ne istediğinizi belirtirsiniz.
Öznitelikler aracılığıyla niyetinizi beyan edersiniz ve "nasıl" yapılacağı kısmını HTMX kütüphanesi arka planda halleder.
Ne Yapılacak? (Request - İstek Tipi ve Yönü)Web'in temel iletişim dili HTTP protokolüdür.
Standart HTML'de bu dilin kelime dağarcığı oldukça kısıtlıdır; genellikle sadece "Getir" (GET) ve "Gönder" (POST) diyebiliriz.
HTMX ise bu kısıtlamayı kaldırarak elementlere tam bir cümle kurma yeteneği verir.
Burada tanımlanan, eylemin niyetidir.
Biz sadece bir bilgi mi okumak istiyoruz? (hx-get)
Yeni bir kayıt mı oluşturuyoruz? (hx-post)
Var olanı mı değiştiriyoruz? (hx-put)
Yoksa bir şeyi siliyor muyuz? (hx-delete) Ve en önemlisi, bu talebi sunucunun hangi kapısına (URL) iletiyoruz? Bu aşama, etkileşimin "ne" olduğunu tanımladığımız, eylemin kimliğini belirlediğimiz aşamadır.
Ne Zaman Yapılacak? (Trigger - Tetikleme Mekanizması)Bir eylemin tanımlı olması, hemen gerçekleşeceği anlamına gelmez.
Tarayıcıya, bu isteği başlatacak "kıvılcımın" ne olduğunu söylememiz gerekir.
Standart HTML elementleri varsayılan davranışlara sahiptir; örneğin bir butona tıklanması veya bir formun gönderilmesi beklenir.
Ancak modern arayüzler daha hassastır.
hx-trigger ile zamanı ve koşulları bükebiliriz:
Kullanıcı fareyi elementin üzerine getirdiğinde mi? ( mouseenter )
Klavye tuşuna bastığında mı? ( keyup )
Yoksa element görünür alana (viewport) girdiğinde mi? (revealed) Dahası, HTMX burada akıllı filtreler sunar.
Örneğin:Bir arama kutusunda her harfe basıldığında değil, kullanıcı yazmayı bitirip 500ms beklediğinde isteği gönder diyerek (delay:500ms) sunucuyu gereksiz trafikten koruyan gelişmiş zamanlama stratejileri bu aşamada belirlenir.
Nereye Koyulacak? (Target - Hedef Konumlandırma)Geleneksel web sayfalarında bir linke tıkladığınızda tüm pencere yenilenir; yani hedef "tüm sayfa"dır.
HTMX bu anlayışı değiştirir.
Bir eylemi başlatan element ile o eylemin sonucunun gösterileceği yer aynı olmak zorunda değildir.
Bunu bir uzaktan kumanda metaforuyla açıklayabiliriz: Tuşa kumanda üzerinde basarsınız (Trigger), ancak kanal televizyonda değişir (Target).
hx-target özelliği sayesinde, sunucudan dönen cevabın sayfanın tam olarak hangi köşesine, hangi kutusuna (div, ul, span vb.) yerleştirileceğini CSS seçicileri (ID veya Class) kullanarak belirleriz.
Bu, sayfanın geri kalanına dokunmadan sadece ilgili alanın güncellenmesini sağlar.
Nasıl Değiştirilecek? (Swap - Yerleştirme Stratejisi)Hedef belirlendi ve sunucudan taze HTML içeriği geldi. Peki, bu yeni parça mevcut yapıyla nasıl kaynaşacak? Bu aşama, HTMX'in "cerrahi müdahale" yeteneğidir.
hx-swap ile değişim stratejisini belirleriz:
İçini Değiştir (innerHTML):Kutunun kendisi kalsın, sadece içindeki yazıları/resimleri yenisiyle değiştir. (Varsayılan davranış)
Kendini Değiştir (outerHTML):Kutuyu tamamen sök at, yerine sunucudan gelen yeni kutuyu koy.
Ekleme Yap (beforeend / afterbegin):Mevcut içeriğe dokunma; gelen yeni veriyi listenin en altına veya en başına ekle. (Sonsuz kaydırma veya canlı akışlar için idealdir)
Yok Et (delete):Gelen cevabı önemseme ve hedef elementi sayfadan tamamen sil.
Bu strateji seçimi, kullanıcı deneyiminin ne kadar akıcı ve doğal hissettireceğini belirleyen son ve en kritik adımdır.
Veri Çağırma ve Okuma hx-get
hx-get, web dünyasının en zararsız ve en sık kullanılan eylemidir. Sunucudan sadece bilgi talep eder.
Bu işlem "Idempotent" (Etkisiz) olarak kabul edilir; yani bu isteği defalarca göndermeniz sunucudaki veriyi değiştirmez, silmez veya bozmaz. Sadece "Bana şu sayfayı veya parçayı göster" dersiniz.
Neden Kullanılır?Standart HTML'de bir linke ( <a> ) tıkladığınızda tarayıcı tüm sayfayı beyazlatır, CSS ve JavaScript dosyalarını yeniden indirir ve sayfayı sıfırdan çizer.
hx-get ise bu israfı önler.Sadece ihtiyacınız olan küçük HTML parçasını (örneğin güncel bir haber listesini veya bir modal penceresini) arka planda sessizce alır ve sayfaya yerleştirir.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Attributes (Öznitelikler) HX GET Örnek</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="get-ornek-kutu">
<h3>hx-get Kullanımı</h3>
<p>
Sunucudan veri okumak için kullanılır (Örn: Liste Getir).
</p>
<button class="get-btn" hx-get="/api/kullanicilar" hx-target="#liste-sonucu">
📂 Listeyi Getir
</button>
<div id="liste-sonucu" class="get-sonuc-alani">
Veriler buraya yüklenecek...
</div>
</div>
</body>
</html>
.get-ornek-kutu {
font-family: 'Segoe UI', sans-serif;
border: 1px solid #dcdcdc;
padding: 20px;
border-radius: 8px;
background-color: #f8f9fa;
max-width: 500px;
}
.get-btn {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 15px;
transition: background 0.2s;
}
.get-btn:hover {
background-color: #0056b3;
}
.get-sonuc-alani {
margin-top: 15px;
padding: 15px;
background-color: #fff;
border: 2px dashed #b0c4de;
border-radius: 5px;
color: #6c757d;
text-align: center;
}
.get-ornek-kutu p {
font-size: 0.9em;
color: #555;
margin-bottom: 15px;
}
.get-ornek-kutu h3 {
color: #333;
margin-bottom: 10px;
}
Veri Gönderme ve İşlem Başlatma hx-post
hx-post, sunucuda bir değişiklik yaratma niyetidir.
Yeni bir kaynak oluşturmak, bir işlemi başlatmak veya hassas verileri (şifre gibi) URL satırında görünmeden göndermek için kullanılır.
Postacıya kapalı bir zarf vermek gibidir; zarfın içini sadece alıcı (sunucu) açar.
Formların Ötesinde:Geleneksel webde POST işlemi genellikle <form> etiketine hapsolmuştur.
HTMX ile herhangi bir element bir form gibi davranabilir.
Bir <div> veya bir <button>, beraberinde veri taşıyarak (hx-vals ile) sunucuya POST isteği atabilir.
Bu, "Sepete Ekle" veya "Beğen" gibi anlık etkileşimler için idealdir
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Attributes HX-POST Örneği</title>
</head>
<body>
<div class="post-ornek-kutu">
<div class="post-baslik">Bültene Abone Ol</div>
<p>
Gelişmelerden haberdar olmak için kayıt ol (hx-post).
</p>
<div class="form-grubu">
<input type="email" name="email" class="post-input" placeholder="E-posta adresiniz...">
<button class="post-btn" hx-post="/api/abone-ol" hx-target="#abonelik-mesaji">
Gönder
</button>
</div>
<div id="abonelik-mesaji" class="post-sonuc">
Durum: Bekleniyor...
</div>
</div>
</body>
</html>
.post-ornek-kutu {
font-family: 'Verdana', sans-serif;
border-left: 5px solid #2ecc71;
background-color: #fff;
padding: 25px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
border-radius: 0 10px 10px 0;
max-width: 450px;
}
.post-baslik {
color: #27ae60;
font-size: 1.2rem;
margin-top: 0;
margin-bottom: 5px;
font-weight: bold;
}
.form-grubu {
display: flex;
gap: 10px;
margin-top: 15px;
}
.post-input {
flex: 1;
padding: 10px;
border: 2px solid #eee;
border-radius: 4px;
outline: none;
transition: border-color 0.3s;
}
.post-input:focus {
border-color: #2ecc71;
}
.post-btn {
background-color: #2ecc71;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 1px;
font-size: 0.8rem;
}
.post-btn:hover {
background-color: #27ae60;
}
.post-sonuc {
margin-top: 10px;
font-size: 0.85rem;
color: #7f8c8d;
font-style: italic;
}
.post-ornek-kutu p {
font-size: 0.9rem;
color: #555;
margin: 0;
}
Güncelleme ve Değiştirme hx-put
hx-put, sunucudaki mevcut bir kaynağın üzerine yazma işlemidir.
"Bu kaydı al ve gönderdiğim yeni haliyle değiştir" demektir.
REST mimarisinde bir veriyi güncellemek için standart yöntem budur.
HTML'in Eksik Parçası:HTML standardı, formlar aracılığıyla doğrudan PUT isteği gönderilmesini desteklemez.
Bu, web geliştiricilerinin yıllardır şikayet ettiği bir eksikliktir.
HTMX bu engeli aşar. Artık bir tablo satırını düzenlerken veya kullanıcı profilindeki isminizi değiştirirken, anlamsal olarak en doğru metot olan PUT'u kullanabilirsiniz.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Attributes HX-PUT Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="put-kart-kutusu">
<div class="put-baslik">
✏️ Profil Düzenle
</div>
<p style="font-size:0.9rem; color:#666; margin-bottom:15px;">
Kullanıcı adınızı değiştirmek için kutuyu düzenleyip kaydedin.
</p>
<div class="put-form-satiri">
<input type="text" name="username" class="put-input" value="kod_yazari_99">
<button class="put-btn" hx-put="/api/profil/guncelle" hx-target="#guncelleme-durumu">
Kaydet
</button>
</div>
<div id="guncelleme-durumu" class="put-durum">
Değişiklik bekleniyor...
</div>
</div>
</body>
</html>
.put-kart-kutusu {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #fff;
border: 1px solid #eee;
border-left: 5px solid #ff9800;
border-radius: 8px;
padding: 20px;
max-width: 400px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.put-baslik {
color: #e65100;
font-size: 1.1rem;
margin-top: 0;
margin-bottom: 15px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.put-form-satiri {
display: flex;
gap: 10px;
align-items: center;
background: #fff3e0;
padding: 10px;
border-radius: 6px;
}
.put-input {
border: none;
background: transparent;
outline: none;
font-size: 1rem;
color: #555;
flex: 1;
font-weight: 500;
}
.put-input {
border-bottom: 2px solid #ffe0b2;
}
.put-input:focus {
border-bottom: 2px solid #ff9800;
}
.put-btn {
background-color: #ff9800;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
font-size: 0.85rem;
transition: background 0.2s;
}
.put-btn:hover {
background-color: #f57c00;
}
.put-durum {
margin-top: 10px;
font-size: 0.8rem;
color: #999;
text-align: right;
}
Veri Silme ve Yok Etme hx-delete
hx-delete, sunucudaki bir kaynağın kalıcı olarak kaldırılması emridir.
Oldukça keskin bir işlemdir.
Başarılı bir silme işleminden sonra, sunucu genellikle ya boş bir yanıt döner ya da kalan listeyi güncelleyen bir içerik gönderir.
Arayüz Davranışı:HTMX ile silme işlemi yaptığınızda, genellikle silinen öğenin ekrandan da anında kaybolmasını istersiniz.
Bu yüzden hx-delete genellikle hx-swap="outerHTML" (kendini değiştir) stratejisiyle birlikte kullanılır.
Eğer sunucu boş yanıt dönerse, element sayfadan "buharlaşır".
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Attributes HX-DELETE Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="delete-container">
<h3>hx-delete: Yorum Silme</h3>
<p>Butona basıldığında bu kart tamamen yok olur.</p>
<div id="yorum-101" class="delete-kart">
<div class="delete-icerik">
<span class="kullanici-adi">Ali Veli</span>
<span class="yorum-metni">"Bu özellik gerçekten hayat kurtarıcı!"</span>
</div>
<button class="sil-btn" hx-delete="/api/yorum/sil/101" hx-target="#yorum-101" hx-swap="outerHTML">
🗑 Sil
</button>
</div>
</div>
</body>
</html>
.delete-kart {
font-family: 'Segoe UI', sans-serif;
background-color: #fff;
border: 1px solid #ffccd5;
border-radius: 8px;
padding: 15px;
max-width: 450px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 4px rgba(220, 53, 69, 0.1);
margin-bottom: 10px;
transition: opacity 0.3s ease;
}
.delete-icerik {
flex: 1;
}
.kullanici-adi {
font-weight: bold;
color: #333;
font-size: 0.95rem;
display: block;
}
.yorum-metni {
color: #666;
font-size: 0.9rem;
margin-top: 4px;
display: block;
}
.sil-btn {
background-color: #fff;
color: #dc3545;
border: 1px solid #dc3545;
padding: 8px 15px;
border-radius: 5px;
cursor: pointer;
font-size: 0.85rem;
font-weight: 600;
margin-left: 15px;
transition: all 0.2s;
}
.sil-btn:hover {
background-color: #dc3545;
color: #fff;
}
.delete-container h3 {
color: #dc3545;
margin-top: 0;
}
.delete-container p {
color: #dc3545;
margin-top: 0;
}
Hedef Konumlandırma hx-target
Varsayılan olarak (default), HTMX isteği yapan elementin bencil olduğunu varsayar; yani cevabı alıp kendi içine yazar.
Ancak modern arayüzlerde eylem bir yerde, sonuç başka bir yerdedir.
hx-target, bu bağı koparır ve "Ben tetikliyorum ama cevabı şuraya yaz" demenizi sağlar.
Uzaktan Etki (Action at a Distance):Bu özellik sayesinde ekranın sol menüsündeki bir linke tıklayarak, sağ taraftaki ana içerik alanını güncelleyebilirsiniz.
Veya bir form gönderildiğinde formun kendisini değil, sayfanın tepesindeki "Bildirim Alanı"nı güncelleyebilirsiniz.
Hedef belirlerken standart CSS seçicileri (#id, .class) kullanılır.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Attributes HX-TARGET Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="target-container">
<div class="target-menu">
<h3>Menü (Tetikleyici)</h3>
<button class="target-menu-btn" hx-get="/api/profil-ozeti" hx-target="#ana-panel">
👤 Profil Özeti
</button>
<button class="target-menu-btn" hx-get="/api/bildirimler" hx-target="#ana-panel">
🔔 Son Bildirimler
</button>
</div>
<div id="ana-panel" class="target-panel">
<p>Lütfen soldaki menüden bir seçenek seçiniz.</p>
</div>
</div>
</body>
</html>
.target-container {
font-family: 'Segoe UI', sans-serif;
background-color: #f3e5f5;
border-radius: 10px;
padding: 20px;
max-width: 650px;
display: flex;
gap: 15px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.target-menu {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
}
.target-menu-btn {
background-color: #512da8;
color: white;
border: none;
padding: 12px 15px;
border-radius: 6px;
text-align: left;
cursor: pointer;
transition: background 0.3s;
font-weight: 500;
font-size: 0.95rem;
}
.target-menu-btn:hover {
background-color: #4527a0;
}
.target-panel {
flex: 2.5;
background: white;
border-radius: 8px;
padding: 20px;
border: 2px dashed #ce93d8;
color: #4a148c;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
min-height: 150px;
font-style: italic;
}
.target-container h3 {
font-size: 1.1rem;
color: #4a148c;
margin-top: 0;
}
Tetikleme Mekanizması hx-trigger
HTML elementleri varsayılan tetikleyicilere sahiptir (Buton için click, Input için change).
hx-trigger, bu varsayılanları geçersiz kılarak veya genişleterek kontrolü size verir.
İletişimin "ne zaman" başlayacağının kural setidir.
Gelişmiş Tetikleyici Niteleyicileri (Trigger Modifiers):Standart olayların davranışını değiştirmek ve daha akıllı hale getirmek için kullanılan özel anahtar kelimelerdir.
Once (Tek Seferlik)Olayın sadece bir kez tetiklenmesini sağlar.
İşlem gerçekleştiğinde dinleyici (listener) kendini yok eder.
Tekrarlayan tıklamaları veya mükerrer işlemleri önlemek için kullanılır.
Changed (Değişim Kontrolü)Sadece elementin değeri bir önceki duruma göre gerçekten değiştiyse istek gönderir.
Örneğin :Bir input içinde yön tuşlarına basılması veya aynı harfin silinip tekrar yazılması durumunda sunucuyu gereksiz yere rahatsız etmez.
Delay:500ms (Gecikmeli Tetikleme)Kullanıcı işlemi yaptıktan sonra belirtilen süre kadar bekler.
Eğer bu süre dolmadan kullanıcı yeni bir işlem yaparsa (örneğin yazmaya devam ederse), sayaç sıfırlanır.
Buna yazılım dünyasında "Debounce" denir ve sunucu maliyetini inanılmaz düşürür.
Load (Yükleme Anı)Herhangi bir kullanıcı etkileşimi (tıklama, yazma) beklemez.
Element sayfa kaynağına (DOM) yerleştiği an isteği otomatik olarak gönderir.
Sayfa açılışını hızlandırmak için ağır içerikleri sonradan yüklemek (Lazy Load) amacıyla kullanılır.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Attributes HX-TRİGGER Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="trigger-wrapper">
<div class="trigger-header"> ⚡ Akıllı Arama (Trigger) </div>
<label>
Arama Yap (500ms Gecikmeli):
</label>
<input
type="text"
class="smart-input"
name="q"
placeholder="Bir şeyler yazın..."
hx-get="/api/arama"
hx-trigger="keyup changed delay:500ms"
hx-target="#arama-sonuclari"
>
<div class="trigger-badge">hx-trigger="keyup changed delay:500ms"</div>
<div id="arama-sonuclari" class="result-box">
Sonuçlar yazmayı bitirdiğinizde yüklenecek...
</div>
<div
class="hover-btn"
hx-get="/api/ipucu"
hx-trigger="mouseenter once"
hx-swap="innerHTML"
>
🖱️ Sadece bir kez ipucu almak için üzerine gel (mouseenter once)
</div>
</div>
</body>
</html>
.trigger-header {
color: #00695c;
font-weight: 700;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.smart-input {
width: 100%;
padding: 12px;
border: 2px solid #b2dfdb;
border-radius: 6px;
font-size: 1rem;
outline: none;
transition: border-color 0.3s;
box-sizing: border-box;
}
.smart-input:focus {
border-color: #009688;
}
.trigger-badge {
display: inline-block;
background: #00897b;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-family: monospace;
margin-top: 5px;
}
.result-box {
margin-top: 15px;
padding: 15px;
background: white;
border-radius: 6px;
color: #555;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
border: 1px dashed #009688;
}
.hover-btn {
margin-top: 20px;
background: #00695c;
color: white;
padding: 10px;
text-align: center;
border-radius: 6px;
cursor: help;
font-size: 0.9rem;
}
.trigger-wrapper {
font-size: 0.9rem;
color: #004d40;
}
Yerleştirme Stratejisi hx-swap
hx-target ile cevabın nereye gideceğini belirledik. hx-swap ise gelen bu yeni HTML parçasının, hedef elemente nasıl monte edileceğinin cerrahi stratejisidir.
Varsayılan olarak HTMX, hedefin içini boşaltır ve yeni içeriği oraya koyar (innerHTML).
Ancak her senaryo için bu yeterli değildir; bazen listeye ekleme yapmak, bazen de elementin kendisini değiştirmek gerekir.
Temel Yerleştirme Stratejileri (Swap Strategies)HTMX'in "Cerrahi Müdahale" yeteneği burada devreye girer.
Sunucudan gelen cevabın, mevcut sayfaya nasıl entegre edileceğini belirleyen kurallar bütünüdür.
Her strateji farklı bir kullanıcı deneyimi (UX) senaryosu için tasarlanmıştır.
innerHTML (Varsayılan Strateji)"Kabuğu koru, içeriği yenile." mantığı ile çalışır
Hedef elementin (bir div veya ul) kendisi DOM'da sabit kalır.
HTMX, bu elementin içindeki tüm çocuk elementleri ( children ) temizler ve sunucudan gelen yeni içeriği bu boşalan alana yerleştirir.
Bir modal penceresinin içeriğini değiştirmek veya bir kartın içindeki metni güncellemek için idealdir.
outerHTML (Tam Değişim)"Eskiyi yık, yerine yenisini inşa et." mantığı ile çalışır
Hedef elementin kendisi de dahil olmak üzere, o bölgedeki yapıyı tamamen DOM'dan söküp atar.
Yerine sunucudan gelen HTML bloğunu monte eder.
Bir butona tıklandığında o butonun tamamen kaybolup yerine bir "Başarılı" mesajının gelmesi veya bir liste elemanının güncellenip renginin değişmesi gereken durumlarda kullanılır.
beforeend (Sona Ekleme / Append)"Mevcut düzene dokunma, sıranın sonuna ekle." mantığı ile çalışır
Hedef elementin içindeki mevcut içerik korunur.
Yeni gelen veri, mevcut içeriğin en altına (kapanış etiketinden hemen öncesine) eklenir.
En popüler kullanım alanı "Sonsuz Kaydırma" (Infinite Scroll) özelliğidir.
Kullanıcı aşağı indikçe yeni yüklenen gönderiler listenin en altına eklenir.
afterbegin (Başa Ekleme / Prepend)"En günceli en tepeye koy." mantığı ile çalışır
Tıpkı beforeend gibi mevcut içeriği korur, ancak yeni gelen veriyi listenin en başına ( açılış etiketinden hemen sonrasına ) yerleştirir.
Canlı bildirim akışları, Twitter (X) zaman tüneli veya yorumlar listesi gibi en yeni içeriğin en üstte görünmesi gereken "Timeline" yapılarında kullanılır.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Attributes HX-SWAP Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="swap-demo-box">
<h3>hx-swap="beforeend"</h3>
<p>Liste sonuna ekleme yapar (Append).</p>
<ul id="mesaj-listesi" class="swap-list">
<li class="swap-item">📨 İlk Mesaj</li>
<li class="swap-item">📨 İkinci Mesaj</li>
</ul>
<button class="load-more-btn" hx-get="/api/yeni-mesajlar" hx-target="#mesaj-listesi" hx-swap="beforeend">
+ Daha Fazla Yükle
</button>
</div>
</body>
</html>
.swap-demo-box {
font-family: 'Segoe UI', sans-serif;
background: #f8f9fa;
border: 1px solid #dee2e6;
padding: 20px;
border-radius: 8px;
max-width: 400px;
}
.swap-list {
list-style: none;
padding: 0;
margin: 0 0 15px 0;
}
.swap-item {
background: white;
padding: 10px;
margin-bottom: 8px;
border-left: 4px solid #0d6efd;
/* Mavi Çizgi */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
animation: fadeIn 0.5s ease;
}
.load-more-btn {
width: 100%;
padding: 10px;
background-color: #0d6efd;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: 500;
}
.load-more-btn:hover {
background-color: #0b5ed7;
}
.swap-demo-box h3 {
margin-top: 0;
color: #333;
}
.swap-demo-box p {
font-size: 0.9rem;
color: #666;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
İçerik Seçme ve Filtreleme hx-select
Bazen sunucu ("backend"), küçük bir parça yerine tam bir HTML sayfası ( Header, Footer, Sidebar dahil ) gönderebilir.
Ancak siz o sayfadan sadece belirli bir tabloyu veya bir div'i almak istersiniz.
hx-select, sunucudan dönen büyük cevabın içinden sadece istediğiniz kısmı "cımbızla" çekip almanızı sağlar.
Neden Kullanılır?Sunucu tarafında her küçük işlem için özel bir API endpoint'i yazmak yerine, mevcut sayfaları tekrar kullanmanıza olanak tanır.
CSS seçicileri (ID veya Class) ile dönen HTML içinden seçim yapılır, geri kalan kısım çöpe atılır.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Attributes HX-SELECT Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="select-demo-box">
<h3>hx-select: İçerik Seçme</h3>
<p>
Sunucudan tam sayfa gelse bile, sadece <code>#fiyat-etiketi</code> ID'li kısmı alıp gösterir.
</p>
<button class="select-btn" hx-get="/urun-detay-sayfasi" hx-select="#fiyat-etiketi" hx-target="#gosterge">
💰 Sadece Fiyatı Güncelle
</button>
<div id="gosterge" class="data-display">
--- TL
</div>
</div>
</body>
</html>
.select-demo-box {
font-family: 'Courier New', monospace;
background: #282c34;
color: #abb2bf;
padding: 20px;
border-radius: 8px;
max-width: 450px;
border: 1px solid #3e4451;
}
.data-display {
background: #21252b;
padding: 15px;
border: 1px dashed #61afef;
margin-top: 15px;
color: #98c379;
text-align: center;
font-size: 1.2rem;
}
.select-btn {
background-color: #c678dd;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-family: sans-serif;
font-weight: bold;
}
.select-btn h3 {
color: #e06c75;
margin-top: 0;
}
.select-btn p {
font-size: 0.85rem;
}
Bant Dışı Güncelleme (Out of Band Swaps) hx-swap-oob
HTMX'in en gelişmiş özelliklerinden biridir.
Normalde bir istek tek bir hedefi (hx-target) günceller.
Ancak bazen tek bir tıklama ile sayfanın birden fazla yerini aynı anda güncellemek istersiniz.
Örneğin:Bir "Sepete Ekle" butonuna bastığınızda, hem butonun durumu değişmeli hem de sayfanın en tepesindeki "Sepet Sayısı" artmalıdır.
Nasıl Çalışır?Bu özellik, isteği yapan elementin üzerinde değil, sunucudan dönen cevabın içindeki elementlerde tanımlanır.
Eğer sunucudan gelen HTML parçasında hx-swap-oob="true" özniteliğine sahip ve ID'si sayfadaki bir elementle eşleşen bir etiket varsa, HTMX ana hedefi güncellerken, gidip o eşleşen diğer elementi de otomatik olarak değiştirir.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Attributes HX-SWAP-OOB Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="oob-wrapper">
<div id="bildirim-sayisi" class="notification-badge">0</div>
<h3 style="margin-top:0;">Bant Dışı Güncelleme</h3>
<div class="action-area">
<div id="islem-sonucu">Henüz işlem yapılmadı.</div>
<br>
<button class="oob-btn" hx-post="/api/sepete-ekle">
🛒 Sepete Ekle
</button>
</div>
<div class="info-text">
<strong>Not:</strong> Bu butona basıldığında sunucu iki parça HTML döner. Biri buraya, diğeri sağ üstteki
kırmızı rozete (id="bildirim-sayisi") gider.
</div>
</div>
</body>
</html>
.oob-wrapper {
font-family: 'Segoe UI', sans-serif;
background: white;
border: 2px solid #ffc107;
border-radius: 10px;
padding: 20px;
max-width: 450px;
position: relative;
}
.notification-badge {
position: absolute;
top: -10px;
right: -10px;
background: #dc3545;
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
transition: transform 0.2s;
}
.action-area {
text-align: center;
margin-top: 10px;
}
.oob-btn {
background-color: #ffc107;
color: #333;
border: none;
padding: 10px 20px;
border-radius: 20px;
font-weight: bold;
cursor: pointer;
}
.oob-btn:hover {
background-color: #ffca2c;
}
.info-text {
font-size: 0.85rem;
color: #666;
margin-top: 15px;
background: #fff9db;
padding: 10px;
border-radius: 5px;
}
Kullanıcı Etkileşimleri Statik Sayfalardan Canlı ve Akışkan Deneyimlere Geçiş
Web'in kalbi, istemci ve sunucu arasındaki sonsuz bir dansta atar.
Bu, özünde dijital bir "Etki-Tepki" mekanizmasıdır.
Kullanıcı bir butona tıklar, bir form doldurur veya sadece fareyle bir öğenin üzerinde duraklar (Etki); sunucu ise bu niyeti algılar, işler ve bir sonuç üretir (Tepki).
Ancak geleneksel web modelinde bu döngü genellikle hantal ve maliyetlidir.
En basit bir veri güncellemesi veya bir "Beğen" butonuna tıklamak bile, tarayıcının tüm sayfayı beyazlatıp, CSS, JavaScript ve görselleri yeniden indirdiği, kullanıcının o anki odak noktasını kaybettiği kesintili bir "Tam Sayfa Yenileme" (Full Page Refresh) süreciyle sonuçlanır.
Bu durum, modern kullanıcının beklediği akıcılığa tamamen aykırıdır.
HTMX, bu kaba döngüyü mikro etkileşimlere indirgeyerek devrim yaratır. Sayfanın tamamını yıkıp yeniden yapmak yerine, cerrahi bir hassasiyetle sadece değişmesi gereken milimetrik alanı günceller.
Bu, sayfanın akıcılığını, scroll pozisyonunu veya kullanıcının dikkatini bozmadan; istemci ile sunucu arasında sürekli, kesintisiz ve canlı bir diyalog kurulmasını sağlar.
Tıklama ile İçerik Yükleme Kesintisiz Akış ve "State of Flow"
Web deneyiminin en temel atomu "tıklamak"tır.
İnternetin ilk günlerinden beri, mavi bir linke tıklamak, tarayıcının o anki gerçekliği yok edip (beyaz sayfa), yeni bir gerçekliği sıfırdan inşa etmesi (sayfa yüklenmesi) anlamına geliyordu.
Bu durum, teknik olarak işlevsel olsa da, kullanıcı psikolojisinde "Bilişsel Kesinti" (Cognitive Interruption) yaratır.
"State of Flow" (Akış Hali) ve Web Psikolojide "Akış", kişinin bir eyleme tamamen odaklandığı ve zaman algısının kaybolduğu zihinsel durumu ifade eder.
Bir web uygulamasında kullanıcının bu akışta kalabilmesi için etkileşimlerin pürüzsüz olması gerekir.
Geleneksel web modelindeki her tam sayfa yenilenmesi (Full Page Refresh), kullanıcının dikkatini dağıtan, odaklanma sürecini sıfırlayan ve akışı bıçak gibi kesen bir "sanal kasis"tir.
HTMX, tıklama eylemini yeniden tanımlayarak bu kasisleri ortadan kaldırır. HTMX ile bir butona veya linke tıklandığında:
Bağlamsal Bütünlük Korunur:Kullanıcı bulunduğu sayfadan koparılmaz. Sayfanın scroll (kaydırma) pozisyonu değişmez, odaklandığı metin kaybolmaz.
Cerrahi Güncelleme:Tarayıcı, sunucudan tüm sayfayı (Header, Footer, Sidebar, Scriptler) tekrar istemek yerine, sadece kullanıcının talep ettiği o küçük bilgi parçasını ister.
Algısal Hız:Tüm sayfanın render edilmesini beklemek yerine, sadece ilgili div güncellendiği için, kullanıcı uygulamayı web sitesi gibi değil, bilgisayarına kurulu yüksek performanslı bir masaüstü yazılımı (Native App) gibi hisseder.
Input Değiştikçe Sunucuya İstek Gönderme Aktif Arama ve Canlı Doğrulama
Geleneksel web formları, kullanıcı ile sunucu arasında "sessiz bir anlaşma" gibidir.
Kullanıcı tüm veriyi yazar, "Gönder" butonuna basar ve sonucu bekler.
Eğer bir hata varsa, sunucu sayfayı yeniler ve kullanıcıya hatayı gösterir. Bu; "Doldur -> Gönder -> Bekle -> Düzelt" şeklinde işleyen, hantal ve geri bildirimin geciktiği bir döngüdür.
Kullanıcı, tüm formu doldurana kadar yaptığı hatalardan habersizdir.
HTMX, bu süreci kökten değiştirerek "Yazarken Etkileşime Gir" ( Interact while Typing ) modeline dönüştürür.
Artık input alanları (<input>, <textarea>, <select>) sadece veri depolayan pasif kutucuklar değil; her değişikliği dinleyen ve sunucuyla konuşan aktif birer ajandır.
Formlar, doldurulması gereken soğuk, bürokratik belgeler olmaktan çıkıp; kullanıcıyla anlık diyalog kuran, yönlendiren ve konuşan canlı arayüzlere evrilir.
Anlık Geri Bildirim:Hata Yapmayı Engellemek Kullanıcı deneyiminin altın kuralı, hatayı oluşmadan engellemektir.
HTMX ile bir kullanıcı adı seçerken veya şifre belirlerken, kullanıcı daha yazma aşamasındayken sunucu arka planda devreye girer.
Senaryo:Kullanıcı "ahmet123" yazarak kullanıcı adını belirler.
HTMX:Her tuş vuruşunda veya odaktan çıkıldığında sunucuya sorar: "Bu isim boşta mı?"
Sonuç:Kullanıcı daha "Kaydol" butonuna basmadan, inputun hemen yanında "Bu isim kullanılabilir" veya "Maalesef dolu" mesajını görür.
Bu, kullanıcının hayal kırıklığını ( frustration ) önler ve formun başarıyla tamamlanma oranını artırır.
Debounce (Akıllı Bekleme):Sunucuyu ve Arayüzü Korumak "Her tuşa basıldığında sunucuya gitmek" kulağa hoş gelse de, teknik olarak tehlikelidir.
Bir arama kutusunda kullanıcı "elma" yazarken; sunucuya sırasıyla "e", "el", "elm", "elma" için 4 ayrı istek göndermek hem sunucuyu boğar hem de ekranda titremelere yol açar.
HTMX, delay (gecikme) mekanizması ile bu sorunu "Debounce" tekniğini kullanarak çözer.
Mantık:"Kullanıcı yazmayı bitirene kadar bekle." bu mantık ile sunucu maliyetlerini radikal şekilde düşürürken, kullanıcıya çok daha kararlı ve sakin bir arayüz deneyimi sunar.
Örneğin:500ms'lik bir gecikme tanımlandığında (delay:500ms), HTMX kullanıcı yazarken bekler.
Kullanıcı elini klavyeden çektiği an, sayaç dolar ve tek bir istek gönderilir.
Fayda:Bu strateji, sunucu maliyetlerini radikal şekilde düşürürken, kullanıcıya çok daha kararlı ve sakin bir arayüz deneyimi sunar.
Form Gönderimlerini Otomatik Hale Getirme Sayfa Yenilemeden Veri İşleme
Web'in şafağından beri formlar ( <form> ), internetin veri taşıyıcı "kamyonları" olmuştur.
Bir giriş yapmak, sipariş vermek veya mesaj göndermek için her zaman formlara güvendik.
Ancak bu güvenilir yapının tarihsel bir kusuru vardır: "Yıkıcı Davranış" (Destructive Behavior).
Geleneksel bir form gönderildiğinde, tarayıcı veriyi paketler, sunucuya yollar ve karşılığında gelen yanıtla tüm sayfayı yeniden çizer.
Bu, kullanıcının o anki bağlamını, kaydırma (scroll) pozisyonunu ve diğer geçici verilerini yok eden sarsıcı bir deneyimdir.
HTMX, formların bu ilkel davranışını ehlileştirir ve onları "Yerinde Güncelleme" (In-Place Update) yeteneğiyle donatır.
Artık veri göndermek, sayfayı terk etmek demek değildir.
Inline Editing (Yerinde Düzenleme):Bağlamdan Kopmadan Değişim Modern uygulamaların (örneğin Trello veya Notion) en sevilen özelliği, veriyi bulunduğu yerde düzenleyebilmektir.
HTMX ile bu deneyim standart hale gelir.
Senaryo:Bir kullanıcı profil kartını inceliyor bu senaryo ile kullanıcı profil kartını düzenleyebilir.
Eylem:"Düzenle" butonuna bastığında, ayrı bir "Düzenleme Sayfası"na yönlendirilmez.
Kartın içindeki metinler (<span>), anında düzenlenebilir kutulara (<input>) dönüşür. Sonuç:Veriyi değiştirip "Kaydet" dediğinde, sayfa yenilenmez.
Form, arka planda sunucuya gider, güncellenmiş HTML parçası döner ve eski kartın yerini alır.
Kullanıcı, sanki bir masaüstü yazılımında çalışıyormuş gibi akıcı bir düzenleme deneyimi yaşar.
Cerrahi Hata Yönetimi:Kaybolmayan Odak Form doğrulama (Validation), kullanıcı deneyiminin en kırılgan noktasıdır.
Klasik yöntemde, hatalı bir form gönderildiğinde sayfa yenilenir ve kullanıcı kendini sayfanın en tepesinde bulur; hata mesajını görmek için aşağı kaydırması gerekir.
HTMX ile sunucu, formun tamamını değil, sadece hatanın olduğu parçayı döndürebilir.
Örneğin:"E-posta geçersiz" hatası, sayfa pozisyonu bozulmadan, kullanıcının odağını kaybetmeden, tam olarak ilgili kutucuğun yanında belirir.
Bu, sunucu tarafı doğrulamanın (Server-Side Validation) gücünü, istemci tarafı (Client-Side) hızında sunar.
Karmaşıklığa Veda:HTML'in Gücüne Dönüş Günümüz Frontend dünyasında, sadece bir formu sayfa yenilemeden göndermek için devasa JavaScript kütüphaneleri (Formik, React Hook Form vb.) ve karmaşık durum yönetimi (State Management) kodları yazılmaktadır.
HTMX, hx-post (Gönder) ve hx-target (Sonucu Buraya Yaz) özelliklerini formlara uygulayarak bu mühendislik karmaşasını ortadan kaldırır.
HTML'in kendi gücüyle, tarayıcıyla savaşarak değil, onunla uyum içinde çalışarak modern form deneyimleri sunar.
Hover, Focus ve Load Gibi Trigger Örnekleri Kullanıcı Niyetini Okumak
Bir web sayfasındaki etkileşim, sadece kullanıcının sol fare tuşuna basmasıyla (Click) veya klavyede bir şeyler yazmasıyla sınırlı değildir.
Kullanıcı, fareyi hareket ettirişiyle, bir kutucuğa odaklanmasıyla veya sadece sayfada gezinmesiyle de sistemle sürekli bir "beden dili" iletişimi halindedir.
HTMX, hx-trigger mekanizması ile bu pasif ve sessiz eylemleri yakalar; onları anlamlı, aktif sunucu isteklerine dönüştürür.
Bu, uygulamanızın kullanıcıdan komut bekleyen pasif bir araçtan, kullanıcının niyetini sezen ve ona göre şekillenen akıllı bir asistana dönüşmesini sağlar.
Hover (mouseenter) - Niyeti Sezmek ve "Algılanan Hız"Kullanıcı deneyiminde "hız" ile "algılanan hız" (Perceived Performance) farklı kavramlardır.
Kullanıcı faresini bir "Profil Detayları" butonunun üzerine getirdiğinde, henüz tıklamasa bile niyeti %90 oranında bellidir: O butona tıklayacaktır.
Geleneksel web, tıklamayı bekler.
HTMX ise bu milisaniyelik süreyi avantaja çevirir.
Prefetching (Ön Yükleme):mouseenter tetikleyicisi ile kullanıcı daha butona dokunmadan, HTMX arka planda sunucuya gidip gerekli veriyi çeker.
Sonuç:Kullanıcı butona tıkladığı o mikrosaniye içinde veri zaten tarayıcıya inmiş ve hazırdır.
İçerik, insan gözüne "anında" (instant) açılmış gibi görünür. Bu "sihirli" hız hissi, uygulamanın kalitesini zirveye taşır.
Focus - Odaklanma Anında RehberlikModern arayüz tasarımının en büyük düşmanı "Gürültü"dür. Bir form sayfasını, her alan için yüzlerce satırlık yardım metniyle doldurmak kullanıcıyı korkutur ve yorar.
HTMX, "Tam Zamanında Bilgi" (Just-in-Time Information) prensibini uygular.
Senaryo:Ekranda temiz bir form vardır. Kullanıcı ne zaman ki karmaşık bir input alanına tıklar (odaklanır / focus); HTMX o an sunucudan o alana özel ipuçlarını, şifre kurallarını veya rehberlik metnini çeker ve yanına getirir.
Fayda:Kullanıcı işini bitirip başka alana geçtiğinde bu bilgi kaybolabilir.
Böylece ekran daima temiz, sadece ihtiyaç duyulan bilginin olduğu sade bir çalışma alanına dönüşür.
Load & Revealed - Tembel Yükleme (Lazy Loading) ve Kaynak YönetimiBir web sayfası açıldığında, kullanıcının o an görmediği (sayfanın en altındaki) grafiklerin, haritaların veya yorumların yüklenmesi kaynak israfıdır ve ilk açılışı (First Contentful Paint) yavaşlatır.
HTMX, JavaScript dünyasında karmaşık "Intersection Observer API" kodları yazarak çözülen bu sorunu, HTML'de tek bir kelimeyle çözer: revealed.
Mekanizma:Sayfa yüklenir, ancak ağır içerikler boştur.
Kullanıcı sayfayı aşağı kaydırıp o bölgeye yaklaştığı an (revealed), HTMX otomatik olarak sunucudan içeriği ister ve yerine koyar.
Load:Veya sayfa iskeleti yüklendiği an (load), ana içeriği parça parça çekerek kullanıcının boş bir beyaz ekranla karşılaşmasını engeller.
Bu strateji, özellikle mobil cihazlarda veri tasarrufu ve yüksek performans sağlar.
Swap (İçerik Güncelleme) Türleri DOM Manipülasyon Sanatı
Bir web sayfasını statik bir belge değil, nefes alan ve sürekli değişen canlı bir organizma gibi düşünelim.
HTMX'in hx-swap mekanizması, bu organizmaya dışarıdan (sunucudan) gelen yeni bir parçanın nakil edilme yöntemini belirler.
Sunucudan taze bir HTML bloğu geldiğinde, tarayıcı bu parçayı mevcut yapının neresine ve ne şekilde monte edecektir? Bu karar, kullanıcı deneyiminin ne kadar akıcı olacağını belirleyen en kritik andır.
Neden "Swap" (Değiş-Tokuş)?Geleneksel web modelinde sayfa her seferinde sıfırdan çizilir.
Modern JavaScript yaklaşımında ise bu işlem için document.getElementById('kutu').innerHTML = veri gibi manuel ve hataya açık kodlar yazılır.
HTMX ise bu karmaşık DOM manipülasyonlarını, sadece bir öznitelik (attribute) değişikliği ile yönetilen, deklaratif (bildirimsel) ve standart bir sürece dönüştürür.
Konumlandırma Stratejisinin ÖnemiSunucudan gelen veri her zaman "eskinin yerine geçen" bir şey olmak zorunda değildir.
Bazen bir listenin sonuna eklenmesi gerekir (Sonsuz Kaydırma), bazen en başına tutturulması gerekir (Canlı Bildirimler), bazen de elementin kendisini tamamen yok etmesi gerekir (Silme İşlemi).
hx-swap, geliştiriciye tarayıcı motoruna şu emri verme gücünü tanır: "Gelen bu yeni parçayı al ve tam olarak şuraya, şu yöntemle yerleştir."
Swap (İçerik Güncelleme) Türleri Varsayılan Yaklaşım Olan (innerHTML)
"Kabuğu Koru, Özü Yenile" ana felsefesi budur ve yapının temelini oluşturur.
hx-swap özniteliği belirtilmediğinde, HTMX'in başvurduğu standart davranış budur. Bu yaklaşım, DOM manipülasyonunun en güvenli ve en az yıkıcı yöntemidir.
Mantığı, bir tablonun üzerindeki yemekleri değiştirmek gibidir; masa (hedef element) sabit kalır, sadece üzerindekiler (içerik) yenilenir.
Teknik Derinlik:Tarayıcı, sunucudan gelen HTML yanıtını alır ve hedeflediğiniz elementin (hx-target ) açılış <...> ve kapanış <...> etiketleri arasında kalan her şeyi temizler. Ardından yeni içeriği bu boşluğa yerleştirir.
Korumacıdır:Hedef elementin sahip olduğu id, class, style veya data- öznitelikleri korunur.
Stabilite:Hedef element DOM ağacından sökülmediği için, o elemente bağlı harici olay dinleyicileri (event listeners) veya CSS kuralları bozulmadan çalışmaya devam eder.
En Uygun Senaryo:Bir modal penceresinin iskeletini koruyup sadece gövdesini değiştirmek, bir bildirim kutusunun metnini güncellemek veya bir "card" yapısının içindeki verileri yenilemek için idealdir.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Swap: innerHTML Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div id="ana-kutu" class="sabit-kapsayici">
📦 Ben Eski İçeriğim
</div>
<button class="btn-yenile" hx-get="/api/yeni-icerik" hx-target="#ana-kutu" hx-swap="innerHTML">
🔄 İçeriği Yenile
</button>
</body>
</html>
.sabit-kapsayici {
border: 3px solid #3498db;
background-color: #eaf2f8;
padding: 20px;
border-radius: 8px;
margin-bottom: 15px;
text-align: center;
font-weight: bold;
color: #2c3e50;
transition: background-color 0.3s;
}
.btn-yenile {
background-color: #34495e;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
Swap (İçerik Güncelleme) Türleri Tam Değişim (outerHTML)
innerHTML bir odanın mobilyalarını değiştirmekse, outerHTML odayı tamamen yıkıp yerine yeni bir oda inşa etmektir.
Bu yöntem, hedef elementin ( hx-target ) açılış ve kapanış etiketleri de dahil olmak üzere DOM ağacından tamamen sökülüp atılmasını sağlar.
Sunucudan gelen HTML bloğu, silinen bu elementin tam olarak bulunduğu konuma yerleşir.
Teknik Derinlik ve Farklar:Bu strateji daha "yıkıcı"dır ve dikkatli kullanılmalıdır:
Kimlik Kaybı:Hedef element silindiği için, o elementin sahip olduğu id veya class değerleri (eğer gelen yeni HTML'de aynısı yoksa) kaybolur.
Bağlantı Kopuşu:Silinen elemente bağlı olan JavaScript olay dinleyicileri (event listeners) veya CSS stilleri devre dışı kalır.
Çünkü artık o element yoktur; yerine bambaşka bir element (örneğin bir <button> yerine bir <span>) geçmiş olabilir.
En Uygun Senaryo:Bir işlemin "tek yönlü" olduğu durumlar için mükemmeldir.
Örneğin:"Abone Ol" butonuna tıklandığında butonun kaybolup "Abone Olundu" yazısına dönüşmesi veya bir "Görevi Sil" butonuna basıldığında o satırın tamamen yok olması.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Swap: outerHTML</title>
<link rel="stylesheet" href="styles.css?v=1.0.150">
</head>
<body>
<div class="gorev-karti">
<div class="gorev-metni">
📝 Raporları Hazırla
</div>
<button id="btn-aksiyon" class="btn-tamamla" hx-post="/api/gorev/tamamla" hx-target="#btn-aksiyon"
hx-swap="outerHTML">
Tamamla
</button>
</div>
</body>
</html>
.gorev-karti {
font-family: 'Segoe UI', sans-serif;
border: 1px solid #e0e0e0;
border-left: 5px solid #6c5ce7;
padding: 20px;
border-radius: 8px;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
display: flex;
justify-content: space-between;
align-items: center;
max-width: 450px;
}
.gorev-metni {
font-weight: 600;
color: #2d3436;
}
.btn-tamamla {
background-color: #6c5ce7;
color: white;
border: none;
padding: 8px 16px;
border-radius: 20px;
cursor: pointer;
font-size: 0.9rem;
transition: transform 0.2s;
}
.btn-tamamla:hover {
background-color: #5f27cd;
transform: scale(1.05);
}
.rozet-basarili {
background-color: #00b894;
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 0.9rem;
display: inline-flex;
align-items: center;
gap: 5px;
cursor: default;
}
Swap (İçerik Güncelleme) Türleri beforebegin, afterbegin
"Mevcudu Koru, Başa Tuttur" prensibi ile çalışmaktadır.
innerHTML ve outerHTML mevcut içeriği silerken; beforebegin ve afterbegin yapıcı (additive) yöntemlerdir.
Mevcut veriye dokunmadan yeni veriyi eklerler.
Ancak "Baş" kavramı burada ikiye ayrılır: Kutunun içi mi, yoksa dışı mı?
Teknik Ayrım ve Konumlandırma:beforebegin (Kutunun Dışına/Önüne)Sunucudan gelen veriyi, hedef elementin açılış etiketinden hemen öncesine yerleştirir.
Yeni gelen element, hedef elementin bir "kardeşi" (sibling) olur.
Kullanım:Bir formun hemen üzerine hata mesajı eklemek veya bir bölümün (Section) hemen üstüne yeni bir bölüm eklemek için kullanılır.
Teknik Ayrım ve Konumlandırma: afterbegin (Kutunun İçine/Başına):Sunucudan gelen veriyi, hedef elementin içine, ancak mevcut tüm içeriklerin en tepesine (ilk çocuk/first-child olarak) yerleştirir.
Kullanım:En sık kullanılan yöntemlerden biridir.
Sosyal medya akışları (Timeline), yorum listeleri veya canlı log kayıtları gibi en güncel verinin en üstte olması gereken durumlarda kullanılır.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Swap: afterbegin</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="timeline-wrapper">
<div class="editor-area">
<h4>Neler Oluyor?</h4>
<button class="btn-tweet" hx-get="/api/yeni-gonderi" hx-target="#zaman-tuneli" hx-swap="afterbegin">
Paylaş
</button>
</div>
<div id="zaman-tuneli" class="feed-container">
<div class="post-card">
<div class="avatar">👤</div>
<div>
<strong>Admin</strong> <span style="color:#657786;">@admin • 2s</span>
<p style="margin:5px 0;">Bu eski bir gönderidir. Yeni gönderi benim üzerime gelecek.</p>
</div>
</div>
</div>
</div>
</body>
</html>
.timeline-wrapper {
font-family: 'Segoe UI', sans-serif;
background-color: #f5f8fa;
max-width: 500px;
margin: 20px auto;
border: 1px solid #e1e8ed;
border-radius: 10px;
padding: 20px;
}
.editor-area {
background: white;
padding: 15px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
border: 1px solid #e1e8ed;
}
.btn-tweet {
background-color: #1da1f2;
color: white;
border: none;
padding: 8px 16px;
border-radius: 20px;
font-weight: bold;
cursor: pointer;
margin-top: 10px;
width: 100%;
}
.btn-tweet:hover {
background-color: #1a91da;
}
.feed-container {
display: flex;
flex-direction: column;
gap: 15px;
}
.post-card {
background: white;
padding: 15px;
border-radius: 10px;
border: 1px solid #e1e8ed;
display: flex;
gap: 10px;
animation: slideDown 0.5s ease-out;
}
.avatar {
width: 40px;
height: 40px;
background-color: #cbd5e0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
}
.timeline-wrapper .editor-area h4 {
margin-bottom: 16px;
font-size: 1.2rem;
color: #657786;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Swap (İçerik Güncelleme) Türleri beforeend, afterend
Yapımız "Akışı Sürdür, Sona Ekle" prensibi ile çalışmaktadır.
Bu iki strateji, web sayfalarının aşağıya doğru uzayan doğasına en uygun yöntemlerdir.
Mevcut içeriği silmezler, aksine var olan yapıyı koruyarak yeni gelen veriyi "kuyruğun sonuna" eklerler.
Ancak burada da kritik soru şudur: Kutunun içine mi, yoksa dışına mı?
Teknik Ayrım ve Konumlandırma:beforeend (Kutunun İçine/Sonuna)Sunucudan gelen veriyi, hedef elementin kapanış etiketinden hemen öncesine ( içerideki son sıraya ) yerleştirir.
Yeni gelen element, mevcut çocukların (children) en sonuncusu olur.
Kullanım:Sonsuz Kaydırma (Infinite Scroll), sohbet uygulamalarındaki yeni mesajlar, log kayıtları veya "Daha Fazla Göster" butonları.
Teknik Ayrım ve Konumlandırma:afterend (Kutunun İçine/Sonuna)Sunucudan gelen veriyi, hedef elementin kapanış etiketinden hemen sonrasına (dışarıya) yerleştirir.
Yeni gelen element, hedef elementin bir sonraki kardeşi (sibling) olur.
Kullanım:Genellikle bir zincirleme reaksiyon yaratmak için kullanılır.
Örneğin:Bir input alanında hata olduğunda, hata mesajını inputun hemen altına (dışına) eklemek veya bir paragrafın altına "Reklam" bandı eklemek.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Swap: beforeend</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="vitrin-wrapper">
<h3>🎨 Sezon Ürünleri</h3>
<div id="urun-listesi" class="urun-grid">
<div class="urun-karti">
<div class="urun-resim">👜</div>
<strong>Deri Çanta</strong><br>
<span">1.200 TL</span>
</div>
<div class="urun-karti">
<div class="urun-resim">👞</div>
<strong>Klasik Ayakkabı</strong><br>
<span>850 TL</span>
</div>
</div>
<button class="load-btn" hx-get="/api/yeni-urunler" hx-target="#urun-listesi" hx-swap="beforeend">
+ Daha Fazla Yükle
</button>
</div>
</body>
</html>
.vitrin-wrapper {
font-family: 'Segoe UI', sans-serif;
max-width: 600px;
margin: 0 auto;
background: #fdfdfd;
padding: 20px;
border: 1px solid #eee;
}
.urun-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-bottom: 20px;
}
.urun-karti {
background: white;
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
text-align: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
animation: fadeIn 0.6s ease-out;
}
.urun-resim {
width: 50px;
height: 50px;
background: #ff7675;
border-radius: 50%;
margin: 0 auto 10px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.2rem;
}
.load-btn {
width: 100%;
padding: 12px;
background-color: #2d3436;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: background 0.3s;
}
.load-btn:hover {
background-color: #000;
}
.vitrin-wrapper h3 {
margin-top: 0;
color: #2d3436;
}
.urun-karti span {
color: #636e72;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Swap (İçerik Güncelleme) Türleri delete & none
Yapımız "Varlığı Sonlandırmak veya Sessiz Kalma" prensibi ile çalışmaktadır.
Şimdiye kadar incelediğimiz tüm stratejiler (innerHTML, afterbegin vb.) DOM'a bir şeyler eklemek veya değiştirmek üzerine kuruluydu.
delete ve none ise bu döngüyü kırar.
Biri var olanı yok eder, diğeri ise gelen cevabı tamamen görmezden gelir.
Teknik Ayrım ve Çalışma Mantığı: delete (Cerrahi Temizlik)HTMX bu komutu aldığında, sunucuya isteği gönderir ve sunucudan ne cevap dönerse dönsün (boş metin, HTML veya hata mesajı), HTMX bu cevabı önemsemez.
Doğrudan hedef elementi ( hx-target ) DOM ağacından söker ve siler.
Kullanım:Yapının bazı kullanım alanlarında "Bu uyarıyı bir daha gösterme", "Listeden sil", "Kapat" butonlarıdır, bunlar kullanılırken cevapları önemsemez.
Teknik Ayrım ve Çalışma Mantığı: none (Hayalet İstek):İstek sunucuya gider, sunucu işlemi yapar ve bir cevap döner.
HTMX, bu cevapla hedef element üzerinde hiçbir değişiklik yapmaz.
Kullanım:Genellikle iki durum için kullanılır: (Analitik/Loglama ve OOB (Bant Dışı) Güncellemeler)
Analitik/Loglama:"Kullanıcı sayfayı gördü" bilgisini sunucuya uçurmak ama ekranda hiçbir şeyi değiştirmemek.
OOB (Bant Dışı) Güncellemeler:İsteği tetikleyen element sabit kalsın ama sayfanın bambaşka bir yerindeki (örneğin sepet ikonundaki) sayı güncellensin istendiğinde kullanılır.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Swap: delete (Gelişmiş Tasarım)</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div id="sistem-bildirimi" class="notification-card">
<div class="notification-icon">
⚠️
</div>
<div class="notification-content">
<div class="notification-title">
Planlı Sistem Bakımı
</div>
<div class="notification-text">
Bu gece 03:00 - 05:00 saatleri arasında sunucularımızda bakım çalışması yapılacaktır.
Lütfen çalışmalarınızı kaydediniz. Anlayışınız için teşekkürler.
</div>
</div>
<button class="btn-close-modern"
hx-delete="/api/bildirim/kapat/99"
hx-target="closest .notification-card"
hx-swap="delete"
title="Bildirimi Kapat">
✕
</button>
</div>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
</body>
</html>
body {
font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f8f9fa;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
.notification-card {
background-color: #ffffff;
border-left: 4px solid #ffc107;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px 25px;
max-width: 500px;
width: 100%;
display: flex;
align-items: flex-start;
gap: 15px;
position: relative;
transition: opacity 0.3s ease, transform 0.3s ease;
}
.htmx-swapping {
opacity: 0;
transform: translateY(-10px);
}
.notification-icon {
color: #ffc107;
font-size: 1.5rem;
flex-shrink: 0;
margin-top: 2px;
}
.notification-content {
flex: 1;
}
.notification-title {
font-weight: 600;
font-size: 1.1rem;
color: #343a40;
margin-bottom: 5px;
}
.notification-text {
font-size: 0.95rem;
color: #6c757d;
line-height: 1.5;
}
.btn-close-modern {
position: absolute;
top: 20px;
right: 20px;
background: transparent;
border: none;
color: #adb5bd;
cursor: pointer;
font-size: 1.2rem;
padding: 5px;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.2s, background-color 0.2s;
}
.btn-close-modern:hover {
color: #495057;
background-color: #f1f3f5;
}
Morfik İçerik Güncelleme (Morph Swaps): Akıllı ve Pürüzsüz Geçişler
Standart yerleştirme stratejileri (innerHTML veya outerHTML), doğaları gereği "kaba kuvvet" ( brute-force ) uygularlar.
Eski içeriği tamamen yok eder ve yeni içeriği sıfırdan oluştururlar.
Statik içerikler için bu mükemmeldir, ancak kullanıcı o an sayfayla etkileşim halindeyse sorunlar başlar.
Odak Kaybı (Focus Loss)Bir arama kutusunda yazı yazarken, o kutuyu içeren bölge güncellenirse, kutu silinip tekrar oluşturulduğu için imleç kaybolur ve klavye odağı gider.
Medya KesintisiOynatılan bir video veya ses dosyası varsa, element yenilendiği için başa sarar veya durur.
Scroll SıçramasıUzun bir listenin ortasındaysanız ve liste yenilenirse, tarayıcı sizi en tepeye atabilir.
Morfik Yaklaşım (Idiomorph Eklentisi ile):Morfik değişim, "DOM Diffing" (Fark Karşılaştırma) adı verilen bir algoritma kullanır.
Sunucudan yeni HTML geldiğinde, HTMX bunu hemen sayfaya basmaz.
Önce mevcut HTML ile yeni gelen HTML'i karşılaştırır.
Sadece değişen kısımları (bir metin, bir sınıf ismi veya bir öznitelik) günceller.
Değişmeyen kısımlara (örneğin kullanıcının o an yazı yazdığı input alanına) dokunmaz.
Avantajı:Durum Koruma (State Preservation)Kullanıcı bir formu doldururken arka planda o formun etrafındaki veriler güncellense bile, kullanıcının imleci, yazdığı yazı ve scroll pozisyonu bozulmaz.
Gereksinim:Bu özellik HTMX çekirdeğinde yer almaz. "Idiomorph" adı verilen harici bir eklenti (extension) gerektirir.
Önemli Not
Bu örnekte klasik HTMX CDN'i ile idiomorph-ext (Akıllı Eklenti) kullanılması
gerekiyor.
Ne İşe Yarayacak? Sunucudan gelen yeni HTML ile ekrandaki eski HTML'i karşılaştırır (Diffing).
Nasıl Çalışacak? Evi yıkıp yeniden yapmaz; sadece duvarın boyasını veya kapının kolunu değiştirir.
Elementleri silmediği için; imleç olduğu yerde kalır, seçili metinler bozulmaz, video/ses oynamaya devam eder.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Morph Swap Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div id="canli-mac-karti" class="live-card" hx-ext="morph">
<div class="score-board">
<div style="font-size:0.9rem; margin-bottom:5px;">
<span class="live-indicator"></span>CANLI (Dakika: 42')
</div>
<div class="teams">Beşiktaş vs Galatasaray</div>
<div class="score">1 - 1</div>
</div>
<p style="font-size:0.9rem; opacity:0.8;">
Siz yorum yazarken yukarıdaki skor değişse bile klavye odağınız kaybolmaz.
</p>
<input type="text" class="chat-input" placeholder="Maç hakkında yorum yap..." autofocus>
<button class="update-btn"
hx-get="/api/skor-guncelle"
hx-target="#canli-mac-karti"
hx-swap="morph:outerHTML">
🔄 Skoru Güncelle (Simülasyon)
</button>
</div>
</body>
</html>
body {
font-family: 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
.live-card {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 16px;
padding: 30px;
width: 400px;
color: white;
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
}
.score-board {
text-align: center;
margin-bottom: 20px;
}
.teams {
font-size: 1.2rem;
margin-bottom: 10px;
font-weight: 300;
}
.score {
font-size: 3rem;
font-weight: bold;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.live-indicator {
display: inline-block;
width: 10px;
height: 10px;
background-color: #ff4757;
border-radius: 50%;
margin-right: 5px;
animation: pulse 1.5s infinite;
}
.chat-input {
width: 100%;
padding: 12px;
border-radius: 8px;
border: none;
background: rgba(255, 255, 255, 0.9);
margin-top: 15px;
box-sizing: border-box;
font-size: 1rem;
}
.update-btn {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.4);
color: white;
padding: 8px 15px;
border-radius: 20px;
cursor: pointer;
margin-top: 20px;
font-size: 0.8rem;
width: 100%;
}
.update-btn:hover {
background: rgba(0, 0, 0, 0.5);
}
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
Sunucu İle Çalışma ve Mimari Veri Değil, Arayüz Taşıyoruz
HTMX kullanmaya başladığınızda, sadece frontend kodlama şekliniz değil, backend (sunucu) tarafındaki mimari yaklaşımınız da kökten değişir.
Modern web geliştirmede (React, Angular, Vue gibi SPA dünyasında) sunucu genellikle "Dilsiz Bir Veri Sağlayıcı" rolündedir; sadece ham veri (JSON) üretir ve arayüzün nasıl görüneceğine karışmaz.
HTMX ise bu rolleri yeniden dağıtır.
HTMX felsefesinde sunucu, uygulamanın beyni ve tasarımcısıdır.
İstemci (tarayıcı) ise sadece sunucunun gönderdiği HTML parçalarını (Hypermedia) ekrana yansıtan akıllı bir çerçevedir.
Bu modelde ağ kablosunun üzerinden (over the wire) geçen şey artık işlenmemiş ham veri değil; stillendirilmiş, anlamlandırılmış ve kullanıcıya sunulmaya hazır arayüz parçacıklarıdır.
Bu yaklaşım, sunucu tarafında hangi programlama dilini (Python, PHP, Node.js, Go, .NET vb.) kullandığınızdan bağımsızdır.
HTMX, sunucunun diliyle ilgilenmez; sunucunun konuşma formatıyla (HTML) ilgilenir. Bu bölümde, JSON tabanlı API mimarisinden, HTML tabanlı Hypermedia
Mimari Zihniyet Değişimi JSON vs HTML
Bir HTMX projesine başlarken geliştiricilerin yaşadığı en büyük şok, yıllardır alışılagelen "API Tasarımı" kurallarının değişmesidir.
Standart bir REST API'de /api/kullanici/1 adresine istek attığınızda sunucu size { "ad": "Ahmet", "durum": "aktif" } gibi bir JSON objesi döner.
Tarayıcı bu veriyi alır, JavaScript ile işler, bir HTML şablonuna gömer ve ekrana basar.
Bu işlem, tarayıcıda ciddi bir işlem gücü ve kod karmaşası gerektirir.
HTMX mimarisinde ise aynı adrese istek attığınızda sunucu size doğrudan şunu döner:
<div class="kullanici-karti">Ahmet <span class="badge">Aktif</span></div>
Geleneksel Modelde: (SPA / JSON API)Sunucu sadece "Ne?" (Veri) sorusunu cevaplayan bir hammadde tedarikçisidir.
Veritabanından çektiği bilgiyi saf, işlenmemiş ve tasarımdan tamamen arındırılmış bir format olan JSON şu şekilde döner:
({"id": 1, "isim": "Ali"}) olarak istemciye fırlatır.
"Nasıl?" (Görünüm) sorusunun tüm yükü ise tarayıcıya (istemciye) bırakılır.
Tarayıcı; bu ham veriyi alıp ayrıştırmak (parse), uygun JavaScript bileşenlerini ( React/Vue Component ) bellekte oluşturmak, veriyi bu şablonlara giydirmek ve en sonunda HTML'e dönüştürüp ekrana basmak gibi ağır bir Client-Side Rendering yükünü sırtlanır.
Bu durum, tarayıcıyı basit bir gösterici olmaktan çıkarıp, karmaşık hesaplamalar yapan bir işlemciye dönüştürür.
"Nasıl?" ( Görünüm ) sorusunu tarayıcıya bırakır.
HTMX Modelinde:Sunucu hem "Ne?" hem de "Nasıl?" sorusunu cevaplar.
Tarayıcıya düşen tek görev, gelen bu hazır parçayı alıp sayfadaki doğru yere yerleştirmektir (Swap).
Bu değişim, backend kodlarınızın "View" ( Görünüm ) katmanını tekrar canlandırmanız gerektiği anlamına gelir.
Artık API endpoint'leriniz sadece veritabanı satırlarını değil, o satırların HTML temsillerini döndürmelidir.
Bu, ilk başta garip gelse de, frontend tarafındaki JavaScript kodunu %90 oranında azalttığı için geliştirme hızını inanılmaz derecede artırır.
Partial HTML Render Mantığı Tam Sayfa vs. Parçacık (Fragment)
Geleneksel web geliştirme yaklaşımlarında (Classic Server-Side Rendering), sunucu her HTTP isteğine standart bir refleksle cevap verir:
Sayfanın tamamını yeniden oluşturmak.
Kullanıcı sadece bir yorum gönderse veya bir ürün listesini filtrelese bile; sunucu <html>, <head>, menüler, yan çubuklar (sidebar) ve <footer> dahil olmak üzere tüm sayfa iskeletini (Layout) sıfırdan derler ve tarayıcıya gönderir.
Bu yaklaşım, sunucu kaynaklarının israfına ve gereksiz ağ trafiğine yol açar.
HTMX ile sunucunuz "Bağlam Farkındalığı" ( Context Awareness ) kazanır.
Sunucu artık her isteğe körü körüne aynı cevabı vermez; isteğin kaynağına ve niteliğine göre "Ne kadar HTML göndermeliyim?" sorusuna karar verir.
Bu karar mekanizması, uygulamanın performansını belirleyen temel faktördür.
Akıllı Sunucu Cevapları ve Karar Anı Sistem şu basit ama güçlü mantık üzerine kuruludur: Sunucu, gelen isteğin başlıklarını (Headers) kontrol eder.
Eğer istek, tarayıcının adres çubuğundan veya bir dış bağlantıdan geliyorsa (İlk Ziyaret), sunucu kullanıcının tam bir arayüze ihtiyacı olduğunu anlar ve eksiksiz bir sayfa şablonu (Full Layout) döndürür.
Ancak istek, sayfa içindeki bir HTMX bileşeninden (örneğin hx-get ile tetiklenen bir butondan) geliyorsa, istek paketinin içinde özel bir imza bulunur: HX-Request: true.
Sunucu bu imzayı gördüğü an stratejisini değiştirir.
"Kullanıcı zaten sayfada, menüleri ve footer'ı tekrar göndermeme gerek yok" der ve sadece değişmesi gereken o küçük HTML parçasını (Fragment) hazırlar.
Pizza Siparişi Metaforu Bu durumu bir restoranda pizza sipariş etmeye benzetebiliriz.
Tam Sayfa Render (Geleneksel):Bu render yöntemiyle sadece bir dilim daha pizza istersiniz.
Ancak garson, bu isteğinizi yerine getirmek için sizi masadan kaldırır, masa örtüsünü değiştirir, çatalları bıçakları yeniler, masayı tekrar donatır ve pizzayı öyle getirir.
Bu hem garson ( sunucu ) için yorucu hem de sizin ( istemci ) için zaman kaybıdır.
HTMX (Kısmi) Render:Garson masadaki düzene ( Layout ) hiç dokunmaz.
Sadece mutfaktan o dilimi bir tabak içinde getirir ve önünüzdeki boşluğa bırakır (Swap).
Masa sabit kalır, deneyim kesintisiz devam eder ve işlem çok daha hızlı tamamlanır.
HTMX ile REST API Tüketimi Veri Odaklı API'den Hypermedia Odaklı API'ye
Birçok geliştiricinin HTMX ile tanıştığında sorduğu ilk ve en haklı soru şudur: "Yıllardır emek verdiğim, yüzlerce endpoint'e sahip REST API'mi çöpe mi atacağım?" Cevap kesinlikle hayırdır.
HTMX, mevcut mimarinizi yıkmayı değil, onu evrimleştirmeyi hedefler.
Mevcut REST mimarisinde kaynaklar ( /api/users/1 ) genellikle saf Veri (Data/JSON) döner.
Bu veri, "ne" olduğunu söyler ama "nasıl" görüneceğini bilmez.
Veri Akışının Evrimi: JSON vs HTML Tablo Gösterimi|
Geleneksel Yol (JSON Serializer)
|
HTMX Yolu (Template Engine)
|
|---|---|
|
Veritabanından veri çekilir
|
Veritabanından veri çekilir
|
|
İş Mantığı çalışır
|
İş Mantığı çalışır
|
|
Veri, ham bir
JSON
nesnesine dönüştürülür
|
Veri, bir
HTML Şablon Motoruna (Template
Engine) gönderilir
|
|
İstemciye gönderilir
|
Şablon motoru veriyi
HTML etiketlerinin içine yerleştirir
|
|
İstemci tarafında JavaScript ile render edilir
|
Hazır
HTML parçası
istemciye gönderilir
|
HTMX ise bu kaynağın Temsilini (Representation/Hypermedia), yani kullanıcıya sunulmaya hazır, stillendirilmiş HTML görünümünü talep eder.
Buradaki anahtar strateji, API'nizi değiştirmek değil, onu "giydirmektir".
Dönüşüm Stratejisi:Görünüm Katmanı (View Layer) Adaptasyonu Backend mimarinizin en değerli kısmı iş mantığıdır (Business Logic); veritabanı sorguları, hesaplamalar ve güvenlik kuralları.
Bu katmana dokunmanıza gerek yoktur. Yapmanız gereken tek şey, veriyi istemciye göndermeden hemen önceki "pakete koyma" aşamasına küçük bir "Görünüm Katmanı" veya "Adapter" eklemektir.
Content Negotiation:Bir Taşla İki Kuş Bu yaklaşım, modern framework'lerin (Django, Express, ASP.NET Core, Spring Boot vb.) sunduğu
"İçerik Pazarlığı" (Content Negotiation) yeteneği ile birleştiğinde muazzam bir güce dönüşür.
Aynı API endpoint'i ( /api/users/1 ), gelen isteğin türüne göre şekil değiştirebilir.
Sunucu, gelen isteğin Accept başlığına veya HTMX imzasına bakar:
Eğer istek bir Mobil Uygulamadan (iOS/Android) geliyorsa, sunucu JSON döner.
Eğer istek HTMX (Web) üzerinden geliyorsa, sunucu HTML döner.
Böylece tek bir backend kodu ve tek bir iş mantığı ile hem modern bir mobil uygulamayı hem de HTMX tabanlı dinamik bir web arayüzünü aynı anda besleyebilirsiniz.
Bu, bakım maliyetlerini düşüren ve kod tekrarını önleyen "Unified Backend" (Birleşik Sunucu) mimarisidir.
Hata Yönetimi ve Loading Durumları: Kullanıcıya "Çalışıyorum" Demek
Kullanıcı deneyiminin en hassas ve kırılgan anı, bir butona basıldığı an ile sunucudan cevabın geldiği an arasında geçen o belirsiz süredir (Latency).
Kullanıcı, sistemin donup donmadığını, isteğinin gidip gitmediğini saniyeler içinde sorgulamaya başlar.
Geleneksel yöntemlerde bu durumu yönetmek için her fetch isteğinin başına ve sonuna spinner açıp kapatan JavaScript kodları yazılırdı.
HTMX, bu durumu "deklaratif" bir standarda bağlayarak, tek bir satır kod yazmadan profesyonel geri bildirim mekanizmaları kurmanızı sağlar.
Yükleme Göstergesi hx-indicator Özniteliği:Görsel Geri Bildirim Sanatı HTMX, bir ağ isteği başladığında ve bittiğinde DOM üzerindeki sınıfları (CSS Classes) otomatik olarak yönetir.
hx-indicator özniteliği, bu süreçte sahneye kimin çıkacağını belirler.
Mekanizma:Siz sayfaya bir "Yükleniyor" ikonu (Spinner) koyarsınız ve CSS ile onu varsayılan olarak gizlersiniz (opacity: 0 veya display: none).
Otomasyon:Bir istek başladığı an, HTMX belirttiğiniz elemente htmx-request adında bir CSS sınıfı ekler.
İstek bittiğinde ise bu sınıfı kaldırır.
Sonuç:Siz sadece CSS geçişlerini (Transition) tanımlarsınız; HTMX ise zamanlamayı yönetir.
Böylece butona basıldığı an ikonun nazikçe belirip, işlem bitince kaybolduğu akıcı bir deneyim oluşur.
Hata Yakalama (Error Handling):Sessizliği Yönetmek Web'in doğasında hatalar vardır (404 Bulunamadı, 500 Sunucu Hatası).
HTMX'in varsayılan davranışı "Güvenli Sessizlik"tir.
Varsayılan Davranış:Eğer sunucu 200 OK dışında bir hata kodu dönerse, HTMX kasıtlı olarak swap işlemini yapmaz.
Neden?Çünkü genellikle sunucular hata anında çirkin hata mesajları veya teknik yığın dökümleri (Stack Trace) gönderir.
HTMX, güzelim arayüzünüzün bu teknik metinlerle bozulmasını engellemek için ekranı korur.
Ancak kullanıcıyı habersiz bırakmak da doğru değildir.
Bu sessizliği yönetmenin iki yolu vardır: (Olay Dinleme (Event Listener) ve HTML Yanıtı (HTMX Yolu))
Olay Dinleme (Event Listener):htmx:responseError olayını dinleyerek, hata anında JavaScript ile özel bir bildirim (Toast Message) gösterebilirsiniz.
HTML Yanıtı (HTMX Yolu):Sunucunuz hata oluşsa bile 200 OK koduyla birlikte, hatayı anlatan şık bir HTML parçası (Kırmızı Uyarı Kutusu) dönebilir.
Böylece hata mesajı da arayüzün doğal bir parçası gibi ekrana yerleşir.
Event (Olay) Sistemi HTMX'in Sinir Sistemi
HTMX, sadece deklaratif (HTML attribute tabanlı) bir kütüphane değildir; aynı zamanda kaputun altında zengin ve yayınlanabilir bir Olay Yönetim Sistemi (Event System) barındırır.
hx-get veya hx-target gibi öznitelikler, HTMX'in görünen yüzüdür ve "ne yapılacağını" tanımlar.
Ancak modern bir web uygulamasının ihtiyaçları, sadece "Veriyi al, şuraya koy" emrinden ibaret değildir.
Kullanıcı arayüzü, arka planda dönen süreçlere karşı duyarlı olmalı; yükleme anında haber vermeli, hata anında uyarmalı ve işlem bittiğinde başka mekanizmaları tetiklemelidir.
İşte Event Sistemi, bu noktada devreye girerek HTMX'in "reflekslerini" oluşturur.
Bir butona tıklanmasından sunucudan cevabın gelmesine, içeriğin DOM'a yerleşmesinden hata oluşmasına kadar her aşama, dinlenebilir ve müdahale edilebilir birer "Olay" (Event) fırlatır, Bu sistem sayesinde:
Şeffaflık Sağlanır:HTTP isteği bir "kara kutu" olmaktan çıkar.
İsteğin ağa düştüğü an, sunucunun cevap verdiği an ve HTML'in değiştiği an; geliştirici için gözlemlenebilir duraklar haline gelir.
Sınırlar Aşılır:HTML'in yeteneklerinin bittiği yerde, JavaScript'in gücü başlar.
Event sistemi, HTMX'in deklaratif basitliğinden ödün vermeden, gerektiğinde JavaScript'in imperatif (komut odaklı) gücüne başvurmanızı sağlayan güvenli bir tüneldir.
HTMX Olayları Genel Açıklama Lifecycle Events
HTMX'in yaşam döngüsü boyunca tetiklediği olaylar, geliştiriciye sürecin her milisaniyesine hakim olma gücü verir.
Bir HTMX etkileşimi, dışarıdan bakıldığında anlık bir "tıkla ve gör" eylemi gibi görünse de, kaputun altında (Under the Hood) titizlikle işleyen, çok aşamalı bir "Boru Hattı" (Pipeline) mimarisi çalışır.
Bu mimari, bir HTTP isteğini basit bir eylem olmaktan çıkarıp, yönetilebilir bir süreç haline getirir.
Süreç şu dört ana fazda gerçekleşir:
Niyet ve Hazırlık:Kullanıcı etkileşime girdiği an süreç başlar. HTMX önce elementi analiz eder, taşınacak verileri (parametreleri) toplar ve doğrulama (validation) kurallarını kontrol eder.
Henüz ağ isteği gönderilmemiştir; bu aşama, geliştiricinin "Dur!" diyebileceği veya isteği modifiye edebileceği son çıkıştır.
Ağ Yolculuğu (Network Request):Hazırlık tamamsa, istek sunucuya doğru yola çıkar. Bu, bekleme süresinin (Latency) başladığı andır.
Arayüzün kullanıcıya "Seni duydum, çalışıyorum" mesajını vermesi (Loading Indicator) gereken aşama burasıdır.
Karşılama ve Karar:Sunucudan cevap döndüğünde, HTMX bu cevabı hemen ekrana basmaz.
Önce paketi açar, durum kodunu (200, 404, 500) kontrol eder ve cevabın geçerli bir HTML olup olmadığına bakar.
Hata varsa akışı durdurur, başarı varsa bir sonraki adıma geçer.
Mutasyon ve Yerleşim (Swap):Bu kısım final aşamasıdır, yeni gelen içerik, belirlenen stratejiye (innerHTML, outerHTML vb.) göre sayfaya monte edilir.
Eski içerik silinir, yeni içerik yerleşir ve varsa yeni gelen JavaScript kodları çalıştırılır (Initialization).
Son Kontrol Noktası ve Karar Anı htmx:beforeRequest(İstekten Önce)
Bir kullanıcı butona bastığında veya bir form gönderdiğinde, HTMX süreci başlatır ancak ağ isteğini hemen yapmaz.
Önce htmx:beforeRequest olayını fırlatır.
Bu, geliştiriciye "İsteği hazırladım, göndermek üzereyim.Bir diyeceğin var mı?" diye sorulan andır.
Bu olay, sunucuya gereksiz trafik gitmesini önlemek ve kullanıcı deneyimini yönetmek için en stratejik noktadır, aşağıda kullanım alanlarını görebilirsiniz.
Doğrulama (Validation):Form verilerini son kez kontrol edip, eksik veya hatalıysa sunucuyu hiç yormadan işlemi iptal edebilirsiniz.
Onay Mekanizması (Confirmation):Kritik işlemlerde (Silme, Abonelik İptali vb.) tarayıcının standart confirm() penceresini veya özel bir modali açarak kullanıcıdan son bir onay alabilirsiniz.
İstek Manipülasyonu:İsteğe dinamik olarak bir "Auth Token" (Kimlik Doğrulama Başlığı) veya o an hesaplanan özel bir parametre ekleyebilirsiniz.
İptal Gücü (preventDefault):Eğer bu olayı dinleyen JavaScript fonksiyonu içinde event.preventDefault() komutu çalıştırılırsa, HTMX "Tamam, vazgeçtim" der ve ağ isteğini (AJAX Request) iptal eder.
Hiçbir veri gönderilmez, sayfa değişmez.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Event: beforeRequest (Premium)</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="premium-danger-card">
<h3 class="danger-title">Hesabı Kalıcı Olarak Sil</h3>
<p class="danger-desc">
Bu işlem geri alınamaz. Tüm verileriniz, geçmişiniz ve ayarlarınız sunucularımızdan kalıcı olarak
silinecektir.
</p>
<button id="silme-butonu" class="btn-premium-delete"
hx-delete="/api/hesabi-sil"
hx-target="body"
hx-swap="innerHTML">
<span>Evet, Hesabımı Sil</span>
</button>
</div>
<script src="script.js?v=1.0.150"></script>
</body>
</html>
body {
background-color: #f9fafb;
padding: 50px;
}
.premium-danger-card {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background-color: #ffffff;
border: 1px solid #fee2e2;
border-radius: 16px;
padding: 32px 24px;
max-width: 420px;
margin: 0 auto;
text-align: center;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.05),
0 8px 10px -6px rgba(0, 0, 0, 0.01);
transition: transform 0.2s ease;
}
.premium-danger-card:hover {
transform: translateY(-2px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}
.danger-title {
color: #1f2937;
font-size: 1.25rem;
font-weight: 700;
margin: 0 0 8px 0;
}
.danger-desc {
color: #6b7280;
font-size: 0.95rem;
line-height: 1.5;
margin: 0 0 24px 0;
}
.btn-premium-delete {
background-color: #dc2626;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 0.95rem;
font-weight: 600;
width: 100%;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 4px 6px -1px rgba(220, 38, 38, 0.3);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-premium-delete:hover {
background-color: #b91c1c;
box-shadow: 0 6px 8px -1px rgba(220, 38, 38, 0.4);
transform: scale(1.01);
}
.btn-premium-delete:active {
transform: scale(0.98);
}
document
.getElementById("silme-butonu")
.addEventListener("htmx:beforeRequest", function (evt) {
// Kullanıcıya tarayıcının native onay kutusunu çıkar
var onay = confirm(
"DİKKAT: Hesabınızı silmek üzeresiniz!\n\nBu işlemden emin misiniz?"
);
if (!onay) {
// Kullanıcı 'İptal' derse, HTMX isteğini durdur.
evt.preventDefault();
console.log("Kullanıcı son anda vazgeçti.");
}
});
Yeni Gelenleri Karşılama ve Entegrasyon htmx:afterSwap (Değişimden Sonra)
Geleneksel web sayfalarında (HTMX olmadan), tüm JavaScript kütüphaneleri (Grafikler, Takvimler, Haritalar) sayfa ilk yüklendiğinde (window.onload veya DOMContentLoaded) başlatılır.
Ancak HTMX ile sayfayı yenilemeden, sonradan yeni HTML parçaları (DOM Elements) eklediğimizde bir sorun oluşur: Mevcut JavaScript kodları bu yeni gelen elementlerden habersizdir.
Örneğin:Sayfa açılışında çalışan bir "Tarih Seçici" kodunuz varsa ve HTMX ile sonradan sayfaya yeni bir tarih inputu yüklerseniz, bu yeni input sıradan bir metin kutusu olarak kalır. (Takvim açılmaz)
Sunucudan gelen cevap işlenip DOM'a yerleştirildiği (Swap edildiği) saniyenin hemen sonrasında tetiklenir.
htmx:afterSwap olayı, tam bu anda devreye girer.
Sunucudan gelen cevap işlenip DOM'a yerleştirildiği (Swap edildiği) saniyenin hemen sonrasında tetiklenir ve "Yeniden Başlatma" , "UI Ayarlamaları" ve "Animasyon Tetikleme" gibi işlemleri gerçekleştirebilirsiniz.
Yeniden Başlatma (Re-initialization):Yeni gelen içerikteki 3. parti eklentileri (Select2, Flatpickr, CKEditor vb.) çalıştırmak.
UI Ayarlamaları:Yeni gelen bir mesaj kutusu varsa, sohbet penceresini otomatik olarak en aşağı kaydırmak (Scroll to bottom).
Animasyon Tetikleme:İçerik geldiği an özel bir "Highlight" (Parlatma) efekti vermek için kullanılabilir.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Event: afterSwap</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="crypto-card">
<div class="card-header">
<span class="card-title">Piyasa Duyarlılığı</span>
<button class="btn-load"
hx-get="/api/analiz-getir"
hx-target="#analiz-kutusu"
hx-swap="innerHTML">
Analiz Et
</button>
</div>
<div id="analiz-kutusu">
<p>Veri bekleniyor...</p>
</div>
</div>
<script src="script.js?v=1.0.150"></script>
</body>
</html>
body {
background-color: #0f172a;
font-family: 'Inter', sans-serif;
display: flex;
justify-content: center;
padding-top: 50px;
}
.crypto-card {
background: linear-gradient(145deg, #1e293b, #0f172a);
border: 1px solid #334155;
border-radius: 16px;
padding: 25px;
width: 400px;
color: #e2e8f0;
box-shadow: 0 10px 30px -10px rgba(0, 255, 255, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
border-bottom: 1px solid #334155;
padding-bottom: 15px;
}
.card-title {
font-size: 1.1rem;
font-weight: 600;
color: #38bdf8;
text-transform: uppercase;
letter-spacing: 1px;
}
.btn-load {
background-color: #0ea5e9;
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s;
font-size: 0.9rem;
}
.btn-load:hover {
background-color: #0284c7;
box-shadow: 0 0 15px rgba(14, 165, 233, 0.4);
}
.progress-container {
background-color: #334155;
border-radius: 10px;
height: 10px;
width: 100%;
margin-top: 10px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #38bdf8, #818cf8);
width: 0%;
border-radius: 10px;
transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.stat-row {
margin-top: 20px;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
document.body.addEventListener("htmx:configRequest", function (evt) {
evt.detail.headers["Content-Type"] = "application/x-www-form-urlencoded";
});
htmx.on("htmx:beforeRequest", function (evt) {
if (evt.detail.requestConfig.path === "/api/analiz-getir") {
evt.preventDefault();
setTimeout(() => {
const mockHTML = `
<div class="stat-row">
<div style="display:flex; justify-content:space-between; font-size:0.9rem; margin-bottom:5px;">
<span>Alım Baskısı</span>
<span style="color:#38bdf8">85%</span>
</div>
<div class="progress-container">
<div class="progress-bar" data-width="85%"></div>
</div>
</div>
`;
document.getElementById("analiz-kutusu").innerHTML = mockHTML;
htmx.trigger(document.body, "htmx:afterSwap", {
target: document.getElementById("analiz-kutusu"),
});
}, 500);
}
});
// ---------------------------------------------------------
// 2. KRİTİK KISIM: htmx:afterSwap EVENT LISTENER
// ---------------------------------------------------------
document.body.addEventListener("htmx:afterSwap", function (evt) {
const barlar = evt.detail.target.querySelectorAll(".progress-bar");
barlar.forEach(function (bar) {
const hedefGenislik = bar.getAttribute("data-width");
setTimeout(() => {
bar.style.width = hedefGenislik;
}, 100);
console.log(`Grafik başlatıldı: ${hedefGenislik}`);
});
});
Sessizliği Yönetmek ve Kullanıcıyı Bilgilendirmek htmx:responseError (Hata Cevabı)
Web'in doğasında hatalar kaçınılmazdır.
Sunucu çökebilir (500), bağlantı kopabilir veya istenen kaynak silinmiş olabilir (404).
Geleneksel AJAX yöntemlerinde, geliştirici hata yönetimini elle kodlamazsa, kullanıcı bozuk bir arayüzle veya tepkisiz bir butonla baş başa kalır.
HTMX'in Varsayılan Güvenli Davranışı:HTMX, sunucudan 200 OK dışında bir yanıt (örneğin 400, 403, 404, 500) aldığında, varsayılan olarak swap işlemini iptal eder.
Neden?Çünkü sunucular hata anında genellikle JSON değil, içinde teknik detayların (Stack Trace) bulunduğu çirkin HTML sayfaları dönerler.
HTMX, bu teknik karmaşanın güzelim arayüzünüzün ortasına yapışmasını engellemek için "ekranı korur" ve içeriği değiştirmez.
Sorun ve Çözüm:Ekran korunur ama kullanıcı "Neden çalışmadı?" diye düşünür.
htmx:responseError olayı, bu sessizliği bozmak için vardır.
Bu olayı dinleyerek; sunucudan gelen hata koduna göre kullanıcıya nazik, kaybolan ve arayüzü bozmayan bir bildirim (Toast) gösterebilirsiniz.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Event: responseError</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="subscribe-card">
<h2 class="subscribe-title">Haftalık Bülten</h2>
<p class="subscribe-desc">Teknoloji dünyasındaki son gelişmelerden haberdar olun.</p>
<div class="input-group">
<input type="email" class="form-input" placeholder="ornek@sirket.com">
<button class="btn-submit" hx-post="/api/olmayan-endpoint" hx-swap="innerHTML">
Kaydol
</button>
</div>
</div>
<div id="toast-area" class="toast-container">
<div id="my-toast" class="error-toast">
<div class="toast-icon">❌</div>
<div class="toast-body">
<span class="toast-title">Hata Oluştu</span>
<span class="toast-message" id="error-text">Sunucu yanıt vermedi.</span>
</div>
</div>
</div>
<script src="script.js?v=1.0.150"></script>
</body>
</html>
body {
font-family: 'Segoe UI', sans-serif;
background-color: #f3f4f6;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.subscribe-card {
background: white;
padding: 40px;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
text-align: center;
max-width: 350px;
width: 100%;
}
.subscribe-title {
color: #111827;
margin-bottom: 10px;
font-size: 1.5rem;
}
.subscribe-desc {
color: #6b7280;
margin-bottom: 20px;
font-size: 0.95rem;
}
.input-group {
display: flex;
gap: 10px;
flex-direction: column;
}
.form-input {
padding: 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 1rem;
outline: none;
transition: border-color 0.2s;
}
.form-input:focus {
border-color: #4f46e5;
}
.btn-submit {
background-color: #4f46e5;
color: white;
border: none;
padding: 12px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.btn-submit:hover {
background-color: #4338ca;
}
.toast-container {
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 10px;
z-index: 9999;
}
.error-toast {
background-color: #fff;
border-left: 5px solid #ef4444;
padding: 16px 20px;
border-radius: 4px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
display: flex;
align-items: center;
gap: 12px;
min-width: 300px;
opacity: 0;
transform: translateX(50px);
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
}
.error-toast.show {
opacity: 1;
transform: translateX(0);
}
.toast-icon {
color: #ef4444;
font-size: 1.2rem;
}
.toast-body {
display: flex;
flex-direction: column;
}
.toast-title {
font-weight: 700;
color: #1f2937;
font-size: 0.9rem;
}
.toast-message {
color: #6b7280;
font-size: 0.85rem;
}
document.body.addEventListener("htmx:responseError", function (evt) {
const xhr = evt.detail.xhr;
const hataKodu = xhr.status;
const statusText = xhr.statusText || "Bilinmeyen Hata";
const toastEl = document.getElementById("my-toast");
const errorTextEl = document.getElementById("error-text");
if (hataKodu === 404) {
errorTextEl.innerText = `Bağlantı hatası (404): Sunucu bulunamadı.`;
} else if (hataKodu === 500) {
errorTextEl.innerText = `Sunucu hatası (500): Lütfen daha sonra deneyin.`;
} else {
errorTextEl.innerText = `Beklenmedik bir sorun: ${hataKodu} - ${statusText}`;
}
toastEl.classList.add("show");
setTimeout(() => {
toastEl.classList.remove("show");
}, 4000);
});
JavaScript Dosyalarına Veda hx-on ile Inline Event Binding
Geleneksel web geliştirmede, bir elementin davranışı ile yapısı birbirinden koparılmıştır.
HTML bir dosyada dururken, o HTML'e "can veren" olay dinleyicileri (addEventListener) bambaşka bir JavaScript dosyasında gizlenir.
Bu durum, bir butona baktığınızda "Bu buton ne yapıyor?" sorusunun cevabını bulmak için dosyalar arasında dedektiflik yapmanıza neden olur (Spooky Action at a Distance).
HTMX'in hx-on özniteliği, bu kopukluğu gidererek "Locality of Behavior" (Davranışın Yerelliği) ilkesini zirveye taşır.
Nasıl Çalışır?hx-on , standart HTML olaylarını (click, change) veya HTMX'e özgü yaşam döngüsü olaylarını (htmx:beforeRequest, htmx:afterSwap) doğrudan elementin üzerinde yakalamanızı sağlar.
Bu özniteliği kullanarak, JavaScript dosyalarına veda ederek, HTML'in doğrudan davranışını yönetebilirsiniz aşağıda kullanma sebeplerimizi açıkladık.
Şeffaflık: Kodun Bilişsel Yükünü AzaltmakBir HTML elementine baktığınızda, onun sadece neye benzediğini (yapı/stil) değil, üzerine tıklandığında ne yapacağını (davranış) da aynı anda görmek, kodun okunabilirliğini radikal şekilde artırır.
Geleneksel yöntemde bir butona tıklanınca hangi JavaScript fonksiyonunun çalıştığını bulmak için proje klasörlerinde "define avına" çıkmanız gerekirken; hx-on ile her şey gözünüzün önündedir.
Bu, "Locality of Behavior" (Davranışın Yerelliği) ilkesinin en saf halidir.
Az Kod: Mikro-Etkileşimler İçin BağımsızlıkBir modal penceresini kapatmak, bir menüyü açmak ( toggle ) veya bir sayacı artırmak gibi basit arayüz işleri için harici .js dosyaları oluşturmak veya Alpine.js / jQuery gibi ek kütüphaneler yüklemek çoğu zaman gereksiz bir karmaşıklıktır ( Overengineering ).
hx-on, tarayıcının kendi dili olan standart JavaScript'i doğrudan HTML üzerinde kullanmanıza olanak tanır.
Bu sayede projenizi dış bağımlılıklardan kurtarır, dosya boyutlarını hafifletir ve basit işleri basit tutmanızı sağlar.
HTMX Entegrasyonu: Yaşam Döngüsüne Doğrudan ErişimHTMX'in zengin olay döngüsüne (Bir istek gitmeden önceki htmx:beforeRequest anına) abone olmak için document.addEventListener yazıp karmaşık seçicilerle (selectors) uğraşmanıza gerek kalmaz.
hx-on, HTMX'in iç mekanizmalarına, tam da olayın gerçekleştiği elementin üzerinden "cerrahi" bir hassasiyetle bağlanmanızı sağlar. Bu, olay yönetimini global bir karmaşadan, yerel ve yönetilebilir bir yapıya dönüştürür.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX hx-on Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="social-card">
<div class="user-avatar">👩💻</div>
<h3>Zeynep Kodluyor</h3>
<p>HTMX ile frontend geliştirmek çok keyifli! #webdev</p>
<button class="btn-like" hx-post="/api/begen" hx-swap="none" hx-on:click="
// 1. Sınıfı değiştir (Kırmızı yap)
this.classList.toggle('liked');
// 2. Sayacı bul ve güncelle
let count = this.querySelector('.count');
let current = parseInt(count.innerText);
// Eğer 'liked' sınıfı eklendiyse artır, yoksa azalt
if(this.classList.contains('liked')) {
count.innerText = current + 1;
this.querySelector('.heart-icon').innerText = '❤️';
} else {
count.innerText = current - 1;
this.querySelector('.heart-icon').innerText = '🤍';
}
">
<span class="heart-icon">🤍</span>
<span class="count">42</span>
</button>
</div>
</body>
</html>
.social-card {
font-family: 'Segoe UI', sans-serif;
background: white;
border: 1px solid #eee;
border-radius: 12px;
padding: 20px;
max-width: 300px;
margin: 40px auto;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
text-align: center;
}
.user-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #e0e7ff;
margin: 0 auto 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
h3 {
margin: 5px 0;
color: #333;
}
p {
color: #666;
font-size: 0.9rem;
margin-bottom: 20px;
}
.btn-like {
background: #f3f4f6;
border: none;
padding: 10px 20px;
border-radius: 30px;
font-size: 1rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
color: #374151;
}
.btn-like:hover {
background: #e5e7eb;
}
.btn-like.liked {
background-color: #fee2e2;
color: #ef4444;
transform: scale(1.1);
}
.heart-icon {
transition: transform 0.2s;
}
.btn-like:active .heart-icon {
transform: scale(0.5);
}
Global ve Target Event Dinleme Kapsam Yönetimi: Olayları Nerede Yakalamalıyız?
HTMX olayları, web tarayıcılarının standart "Event Bubbling" ( Yukarı Doğru Köpürme ) mantığıyla çalışır.
Bir olay (bir istek hatası), önce olayın gerçekleştiği en küçük parçada (bir buton) başlar, sonra kapsayıcısına (form), oradan ana bölüme (div) ve en sonunda sayfanın en tepesine (document.body) kadar tırmanır.
Bu tırmanış, geliştiriciye olayları yakalamak için iki farklı stratejik nokta sunar.
Target (Hedef) Bazlı Dinleme: Nokta Atışı ve İzolasyonBu yaklaşım, olayları genel bir havuzda toplamak yerine, sadece ilgili elementin üzerinde ve kaynağında dinlemektir.
Tıpkı bir cerrahın sadece hasta dokuya müdahale edip vücudun geri kalanına dokunmaması gibi ("Cerrahi Müdahale"), bu yöntem de olayı izole eder.
Etkileşimi başladığı noktada yakalar ve orada işler; böylece sayfanın geri kalanındaki diğer bileşenlerin bu olaydan gereksiz yere haberdar olmasını veya etkilenmesini engeller.
Mantık:Olay dinleyicisini (HTMX'in hx-on özniteliği ile veya JavaScript içindeki addEventListener metoduyla) doğrudan o HTML elementinin etiketine veya spesifik ID'sine bağlarsınız.
Bu sayede, bir elementin ne zaman tetikleneceği ve tetiklendiğinde ne yapacağı, proje klasörlerinin derinliklerinde değil, tam olarak o elementin tanımlandığı yerde, elinizin altında bulunur.
Ne Zaman Kullanılır?Olayın sonucu ve yan etkileri sadece o spesifik bileşeni ilgilendiriyorsa bu yöntem tercih edilmelidir.
Örneğin:Bir "Kaydet" butonuna basıldığında hata oluşursa ve siz sadece o butonun titremesini veya renginin kırmızıya dönmesini istiyorsanız, bu olayı tüm sayfada (Global) dinlemenin bir anlamı yoktur.
Bu yaklaşım, kodunuzu modüler tutar, bileşenlerin birbirine bağımlılığını azaltır ve gelecekteki bakım maliyetlerini düşürür.
Global (Evrensel) Dinleme: Merkezi Kontrol KulesiBu yaklaşım, olayları tek tek elementlerde kovalamak yerine, sayfanın en tepesinde, document.body üzerinde karşılamaktır.
Yazılım mühendisliğindeki "DRY" (Don't Repeat Yourself - Kendini Tekrar Etme) prensibinin zirvesidir.
Tüm sayfayı kapsayan bir "güvenlik ağı" gibi çalışır; aşağıdan gelen her olay, eninde sonunda bu ağa takılır.
Mantık:Bir projede yüzlerce buton veya form olabilir.
Her birine tek tek "Hata olursa uyarı ver" kodu yazmak hem zaman kaybıdır hem de kod tekrarı yaratır.
Bunun yerine, body etiketine tek bir dinleyici (Listener) koyarsınız. Hangi butondan, hangi formdan gelirse gelsin, olay yukarı tırmanırken bu merkezi dinleyici tarafından yakalanır ve işlenir.
Ne Zaman Kullanılır?Uygulamanın genelini ilgilendiren, bileşenden bağımsız durumlar için global dinleme zorunludur.
Örnek 1 (İlerleme Göstergesi):Sayfanın neresinde olursa olsun, herhangi bir istek başladığında ekranın en üstündeki ince mavi ilerleme çubuğunu (Progress Bar - YouTube tarzı) hareket ettirmek için.
Örnek 2 (Hata Yönetimi):Herhangi bir ağ hatasında (Network Error veya 500 Sunucu Hatası), isteği kimin yaptığına bakmaksızın ekranın köşesinde standart bir "Bir hata oluştu, lütfen bağlantınızı kontrol edin" bildirimi (Toast) çıkarmak için.
Custom Event (Özel Olay) Kullanımı Sunucudan İstemciye Emir Vermek
Standart bir HTTP etkileşimi genellikle tek boyutludur: İstemci bir şey ister, sunucu bir HTML parçası döner ve bu parça sayfadaki yerini alır.
Ancak gerçek dünyadaki uygulamalar bu kadar basit değildir.
Bazen bir eylemin ("Kaydet" butonuna basmanın) birden fazla sonucu (Yan Etkisi / Side Effect) olması gerekir.
Sunucu, veritabanına kaydı yaptıktan sonra sadece güncel listeyi (HTML) dönmekle kalmamalı; aynı zamanda tarayıcıya
"Şu açık olan pencereyi kapat" veya "Sağ üstte bir başarı mesajı göster" gibi emirler de verebilmelidir.
Geleneksel yöntemlerde bu durum, karmaşık JavaScript geri çağırma fonksiyonları (callbacks) ile yönetilirdi.
HTMX ise bu sorunu, HTTP protokolünün gücünü kullanarak, HX-Trigger Response Header (Yanıt Başlığı) ile çözer.
Mekanizma: HTML ile Gelen Görünmez EmirlerStandart bir web iletişiminde sunucu genellikle sadece "Görünür İçerik" (HTML Body) gönderir.
HTMX ise HTTP protokolünün "Başlıklar" (Headers) kısmını bir komuta merkezi gibi kullanır.
Sunucu, HTML yanıtını hazırlarken, bu paketin görünmeyen "meta-veri" kısmına özel bir JSON verisi enjekte eder.
Bu veri, basit bir bilgi notu değil; tarayıcıda tetiklenmesi emredilen olayların (Events) kesin bir listesidir.
HTMX, sunucudan gelen bu cevabı karşıladığında bir "Orkestra Şefi" gibi davranır ve iki farklı süreci eş zamanlı olarak yönetir:
Görsel İşlem (Swap):Paketin içindeki HTML içeriğini alır ve sayfadaki hedef kutuya yerleştirir. Bu, kullanıcının gördüğü kısımdır.
Mantıksal İşlem (Trigger):Başlık kısmındaki (Header) gizli emirleri okur.
Bu emirleri alır ve tarayıcının içinde, sanki bir kullanıcı fiziksel bir butona basmış gibi JavaScript olaylarına (Custom Events) dönüştürerek fırlatır.
Bu, uygulamanın hissettiği ve tepki verdiği kısımdır.
Senaryo Üzerinden AnalizBir kullanıcının "Yeni Kayıt" formunu başarıyla gönderdiği anı adım adım inceley elim:
Sunucu (Karar Anı):Veritabanı işlemini tamamlar ve kaydı oluşturur.
Cevap olarak listenin güncel halini (HTML) hazırlar.
Ancak işi bitmemiştir; paketin etiketine şu notu iliştirir: HX-Trigger: {"modalKapat": true, "bildirimGoster": "Başarılı!"}
Tarayıcı (HTMX - İcra Anı):Cevabı açar, önce hx-swap kuralına göre ekrandaki listeyi günceller.
Hemen ardından, görünmez bir el devreye girer ve JavaScript evreninde modalKapat ve bildirimGoster sinyallerini havaya ateşler.
Sonuç (Refleks):Sizin önceden yazdığınız ve pusuda bekleyen basit JavaScript dinleyicileri ( Listeners ) bu sinyalleri havada yakalar.
Biri açık olan modali kapatırken, diğeri sağ üstten yeşil başarı kutucuğunu çıkarır.
Modals, Tabs, Live Search HTMX ile Gerçek Dünya Uygulamaları: Karmaşıklığı Yönetmek
HTMX'in felsefesini, olay döngüsünü ve sunucu ile nasıl konuştuğunu öğrendik.
Şimdi sıra, bu teorik yapı taşlarını birleştirerek modern web uygulamalarının belkemiğini oluşturan arayüz bileşenlerini (Components) inşa etmeye geldi.
Günümüz web standartlarında kullanıcılar; sayfa yenilenmeden açılan pencereler (Modals), anında geçiş yapılan sekmeler (Tabs) ve
yazarken sonuç getiren arama çubukları (Live Search) beklerler.
Geleneksel "Single Page Application" (SPA) dünyasında bu beklentileri karşılamak pahalıdır.
Basit bir modal penceresi için bile tarayıcıda durum yönetimi (State Management), olay dinleyicileri ve karmaşık DOM
manipülasyonları kurgulamanız gerekir.
State (Durum) Nerede Yaşamalı?Bu bölümdeki örneklerin temelindeki felsefe şudur: Uygulamanın durumu (State) tarayıcıda değil, sunucuda kalmalıdır.
React/Vue Yaklaşımı:"Modal açık mı?" bilgisini tarayıcıdaki bir değişkende ( isOpen = true ) tutar ve buna göre HTML çizer.
HTMX Yaklaşımı:"Modal açık mı?" diye sormaz, kullanıcı butona bastığında sunucudan "Açık Modal HTML'ini" ister ve ekrana basar.
Bu bölümde, JavaScript ile tekerleği yeniden icat etmek yerine; HTML'in doğasını kullanarak bu karmaşık bileşenleri nasıl şaşırtıcı
derecede az kodla, deklaratif ve performanslı bir şekilde hayata geçireceğimizi göreceğiz.
"State Senkronizasyonu" problemini ortadan kaldırarak, doğrudan sunucu odaklı (Server-Driven) arayüzler tasarlayacağız.
Modal Pencereleri AJAX ile Doldurma Dinamik ve Hafif Modallar
Bir "Modal Penceresi" ( Pop-up ), genellikle sayfanın geri kalanını karartan ve kullanıcının odağını tek bir noktaya toplayan kritik
bir arayüz elemanıdır.
Ancak bu pencerelerin yönetimi genellikle performans sorunlarına yol açar.
Geleneksel Yöntem (Şişkin DOM):Sayfada kullanılma ihtimali olan onlarca modal (Üye Ol, Giriş Yap, Detay Gör, Silme Onayı vb.) sayfa ilk açıldığında yüklenir ve CSS ile
( display: none ) gizlenir.
Kullanıcı bu butonlara hiç basmasa bile tarayıcı bu gereksiz HTML'i indirmek ve işlemek zorundadır.
HTMX Yöntemi (Lazy Loading):HTMX ile sayfada sadece bir tane boş modal kapsayıcısı ( Container ) bulunur.
İçerik, sadece kullanıcı butona bastığında sunucudan çekilir ve bu kapsayıcıya yerleştirilir.
Bu yöntemi kullanmamızın avantajları aşağıdaki gibidir okumanızı öneririm:
Performans: Görünmez Yükten (DOM Bloat) KurtulmakGeleneksel "Single Page Application" yapılarında, uygulamanın tüm olası senaryoları ( onlarca modal, uyarı penceresi ve
form ) sayfa ilk açıldığı anda yüklenir ve display: none ile gizlenir.
Kullanıcı bu modalların %90'ını asla açmasa bile, tarayıcı bu devasa HTML yığınını indirmek, ayrıştırmak ve bellekte tutmak zorundadır.
HTMX ile "Tembel Yükleme" (Lazy Loading) standart hale gelir. Sayfa ilk açıldığında hafiftir.
Modalın HTML'i, CSS'i ve içeriği; sadece kullanıcı o butona bastığı milisaniyede ağ üzerinden gelir.
Bu, özellikle mobil cihazlarda açılış hızını ( Time to Interactive ) dramatik şekilde artırır.
Güncellik: Bayat Veri Sorununa SonKlasik yöntemde modal içeriği sayfa yüklendiğinde oluştuğu için, kullanıcı modalı açana kadar o veri bayatlamış olabilir.
Örneğin:Bir e-ticaret sitesinde "Hızlı Bakış" modalını açtığınızda, stok bilgisi 10 dakika öncesine ait olabilir.
HTMX mimarisinde ise "Talep Anında Veri" (Just-in-Time Data) prensibi işler, modal her açıldığında sunucuya taze bir istek gider.
Böylece kullanıcı her zaman veritabanındaki en güncel stok durumunu, fiyatı veya yorumları görür, ve "Senkronizasyon" problemi ortadan kalkar.
Basitlik: Durum Yönetimi (State Management)Hijyeni Bir modalı JavaScript ile yönetmek, göründüğünden daha karmaşıktır: "Modal açık mı?", "Hangi z-index'te?",
"İçini ne zaman temizleyeceğim?", "Animasyon bitti mi?" gibi sorularla boğuşmak gerekir.
HTMX yaklaşımında durum (state) sunucudadır.
Tarayıcı, "Modal açık mı?" diye sormaz; sadece sunucudan gelen HTML parçasını ( Modal) ekrana basar.
Kapatıldığında ise o parça yok olur. Karmaşık if/else blokları, isOpen değişkenleri ve CSS z-index savaşları yerini saf ve deklaratif bir akışa bırakır.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Modal Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div style="padding: 50px; text-align: center;">
<div id="modal-container"></div>
<button class="btn-open" hx-get="/api/kullanici-detay" hx-target="#modal-container" hx-swap="innerHTML">
Kullanıcı Kartını Aç
</button>
</div>
</body>
</html>
#modal-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(5px);
opacity: 0;
transition: opacity 0.3s ease;
}
#modal-container.active {
display: flex;
opacity: 1;
}
.modal-content {
background: white;
padding: 0;
border-radius: 12px;
width: 400px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
transform: translateY(20px);
transition: transform 0.3s ease;
overflow: hidden;
font-family: 'Segoe UI', sans-serif;
}
#modal-container.active .modal-content {
transform: translateY(0);
}
.modal-header {
background: #4f46e5;
padding: 20px;
color: white;
font-size: 1.2rem;
font-weight: bold;
}
.modal-body {
padding: 20px;
color: #4b5563;
line-height: 1.6;
}
.modal-footer {
padding: 15px 20px;
background: #f9fafb;
text-align: right;
border-top: 1px solid #e5e7eb;
}
.btn-open {
padding: 12px 24px;
background-color: #4f46e5;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
box-shadow: 0 4px 6px rgba(79, 70, 229, 0.2);
}
.btn-close {
background-color: #e5e7eb;
color: #374151;
padding: 8px 16px;
border-radius: 6px;
border: none;
cursor: pointer;
font-weight: 600;
}
.btn-close:hover {
background-color: #d1d5db;
}
Tab Sistemi Oluşturma İçeriği Böl ve Yönet (Dinamik Sekme Mimaris)
Modern web arayüzlerinde; kullanıcı profilleri, detaylı ayar sayfaları veya yönetim panelleri gibi yoğun veri içeren alanları yönetmenin
en zarif yolu sekmeli (Tab) yapılardır.
Geleneksel yöntemlerde bu yapıyı kurmak genellikle iki uçlu bir performans sorunu yaratır: Ya sayfa açılırken kullanıcının belki de hiç görmeyeceği tüm sekmelerin içeriğini gizlice yükleyip açılışı yavaşlatırsınız ya da her sekme geçişinde sayfayı tamamen yenileyerek akıcılığı bozarsınız.
HTMX, bu ikilemi "Tam Zamanında Yükleme" (Just-in-Time Loading) mimarisiyle çözer.
Bu yaklaşımda sayfanızda sabit bir "İçerik Kapsayıcısı" (Content Container) bulunur.
Sayfa ilk açıldığında bu kutunun içinde sadece varsayılan sekme (örneğin Profil) verisi yer alır; diğer sekmeler (Güvenlik, Bildirimler) tamamen boştur ve sunucudan henüz talep edilmemiştir.
Bu strateji, sayfanızın başlangıç yükünü (Initial Load) inanılmaz derecede hafifletir.
Çalışma Prensibi:Bir Kutu, Sonsuz İçerik Sistemin kalbinde, tüm sekmelerin ortaklaşa kullandığı tek bir hedef alan ( div ) yatar.
Üst kısımdaki sekme butonları, sadece basit birer yönlendiricidir.
Kullanıcı "Güvenlik" sekmesine tıkladığında, tarayıcı sunucuya "Bana sadece güvenlik ayarlarının HTML parçasını ver" der.
Sunucudan dönen bu küçük ve hazır parça, hx-target özelliği sayesinde doğrudan ortak içerik kutusuna yerleşir (innerHTML).
Böylece, kullanıcı sayfadan hiç ayrılmadan, sayfa yenilenmeden ve tarayıcıyı yormadan, sadece ihtiyaç duyduğu veriyi o an çağırarak görüntüler.
Bu mimari, hem sunucu trafiğini azaltır hem de kullanıcıya masaüstü uygulama hızında bir geçiş deneyimi sunar.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Tab Sistemi</title>
<link rel="stylesheet" href="styles.css?v=1.0.150">
</head>
<body>
<div class="tabs-container">
<div class="tabs-header">
<button class="tab-btn active" hx-get="/api/profil" hx-target="#tab-content" onclick="changeTab(this)">
Profil
</button>
<button class="tab-btn" hx-get="/api/guvenlik" hx-target="#tab-content" onclick="changeTab(this)">
Güvenlik
</button>
<button class="tab-btn" hx-get="/api/bildirimler" hx-target="#tab-content" onclick="changeTab(this)">
Bildirimler
</button>
</div>
<div id="tab-content">
<h3>👤 Profil</h3>
<p>Kullanıcı: <strong>Admin</strong></p>
<p>Durum: Aktif</p>
</div>
</div>
<script src="script.js?v=1.0.150"></script>
</body>
</html>
.tabs-container {
font-family: 'Segoe UI', sans-serif;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 8px;
max-width: 500px;
margin: 20px auto;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.tabs-header {
display: flex;
background-color: #f8fafc;
border-bottom: 1px solid #e2e8f0;
}
.tab-btn {
flex: 1;
padding: 15px;
border: none;
background: none;
cursor: pointer;
font-weight: 600;
color: #64748b;
border-bottom: 3px solid transparent;
transition: all 0.2s;
}
.tab-btn:hover {
background-color: #f1f5f9;
color: #334155;
}
.tab-btn.active {
color: #3b82f6;
border-bottom-color: #3b82f6;
background-color: #fff;
}
#tab-content {
padding: 25px;
min-height: 120px;
}
function changeTab(btn) {
document
.querySelectorAll(".tab-btn")
.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
}
document.body.addEventListener("htmx:beforeRequest", function (evt) {
evt.preventDefault();
const path = evt.detail.requestConfig.path;
const target = document.getElementById("tab-content");
target.innerHTML = '<div style="color:#94a3b8;">Yükleniyor...</div>';
setTimeout(() => {
let html = "";
if (path === "/api/profil") {
html =
`<h3>👤 Profil</h3>` +
`<p>Kullanıcı: <strong>Admin</strong></p>` +
`<p>Durum: Aktif</p>`;
} else if (path === "/api/guvenlik") {
html =
`<h3>🔒 Güvenlik</h3>` +
`<p>Son Şifre Değişimi: 30 gün önce</p>` +
`<button>Şifre Değiştir</button>`;
} else if (path === "/api/bildirimler") {
html =
`<h3>🔔 Bildirimler</h3>` +
`<label><input type="checkbox" checked> E-posta Al</label>`;
}
target.innerHTML = html;
}, 300);
});
Canlı Arama (Live Search) Klavye ile Konuşan Arayüzler (Aktif Arama Deseni)
Arama kutuları, kullanıcı deneyiminin en kritik ve en çok etkileşim alan noktalarından biridir.
Modern web standartlarında kullanıcılar, bir şeyler yazdıktan sonra "Ara" butonuna basıp sayfanın yenilenmesini beklemezler.
Beklenti, sistemin kullanıcının yazma hızına ayak uydurması ve sonuçları "canlı" olarak, yazma eylemi devam ederken getirmesidir.
HTMX, bu "Aktif Arama" (Active Search) desenini, karmaşık JavaScript olay dinleyicileri yazmadan, tamamen deklaratif bir yapıyla kurgulamanızı sağlar.
Akıllı Dinleme ve Performans YönetimiBir arama motoru yaparken en büyük risk, sunucuyu gereksiz isteklere boğmaktır.
Kullanıcı "Elma" yazarken her harfte (E, El, Elm, Elma) sunucuya gitmek verimsizdir.
HTMX, bu kaosu yönetmek için input alanını ( <input> ) akıllı bir ajana dönüştüren üç kritik mekanizmayı birleştirir:
Sistem önce kullanıcının tuştan elini çekmesini bekler (keyup).
Ancak her tuş vuruşu yeni bir arama demek değildir; örneğin kullanıcı ok tuşlarına basmış olabilir.
Bu yüzden sistem, sadece veri gerçekten değişmişse ( changed ) harekete geçer.
En önemli mekanizma ise sabırdır; HTMX, kullanıcı yazarken sunucuya gitmez, yazmayı bitirmesini veya duraksamasını bekler ( delay:500ms ).
Yazılım dünyasında "Debounce" olarak bilinen bu teknik, sunucuyu binlerce gereksiz istekten koruyan ve arayüzdeki titremeyi önleyen hayati bir performans kalkanıdır.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Live Search</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="search-container">
<h3 style="margin-top:0; color:#1e293b;">🔍 Kişi Ara</h3>
<input
type="text"
class="search-input"
placeholder="İsim yazın..."
name="q"
hx-get="/api/ara"
hx-trigger="keyup changed delay:500ms"
hx-target="#sonuc-tablosu"
>
<table class="results-table">
<tbody id="sonuc-tablosu">
<tr>
<td style="color:#94a3b8;">Lütfen arama yapın...</td>
</tr>
</tbody>
</table>
</div>
<script src="script.js?v=1.0.150"></script>
</body>
</html>
.search-container {
font-family: 'Segoe UI', sans-serif;
max-width: 400px;
margin: 20px auto;
border: 1px solid #ddd;
padding: 20px;
border-radius: 8px;
}
.search-input {
width: 100%;
padding: 10px;
border: 2px solid #3b82f6;
border-radius: 6px;
font-size: 1rem;
outline: none;
box-sizing: border-box;
}
.results-table {
width: 100%;
margin-top: 15px;
border-collapse: collapse;
}
.results-table td {
padding: 8px;
border-bottom: 1px solid #eee;
}
const userData = [
"Ahmet Yılmaz",
"Ayşe Demir",
"Ali Veli",
"Zeynep Kaya",
"Mehmet Öztürk",
"Canan Dağ",
"Burak Yılmaz",
"Elif Su",
"Cemil Meriç",
"Fatma Gül",
];
htmx.on("htmx:beforeRequest", function (evt) {
const config = evt.detail.requestConfig;
if (config.path.includes("/api/ara")) {
evt.preventDefault();
const term = (config.parameters.q || "").toLowerCase().trim();
let results = [];
if (term) {
results = userData.filter((u) => u.toLowerCase().includes(term));
}
let responseHTML = "";
if (!term) {
responseHTML =
"<tr><td style="color:#94a3b8;">Lütfen arama yapın...</td></tr>";
} else if (results.length > 0) {
responseHTML = results
.map(
(u) => `
<tr>
<td>👤 ${u}</td>
</tr>
`
)
.join("");
} else {
responseHTML = `
<tr>
<td style="color:#ef4444;">
"${config.parameters.q}" için sonuç bulunamadı.
</td>
</tr>
`;
}
document.getElementById("sonuc-tablosu").innerHTML = responseHTML;
}
});
Pagination (Sayfalama) ve Sonsuz Kaydırma Veriyi Parçalara Bölmek (Akışın Sürekliliği)
Modern web uygulamalarında, binlerce satırlık veri setlerini ( büyük listeler, ürün katalogları, yorum akışları) tek seferde
tarayıcıya yüklemek teknik bir intihardır.
Bu durum hem sunucuyu kilitler hem de kullanıcının tarayıcısını dondurur.
Bu yüzden veriyi "parça parça" ve "talep edildikçe" sunmak zorundayız.
HTMX, klasik " Sayfa 1, Sayfa 2, Sayfa 3" linklerine tıklayıp sayfayı yenilemek yerine; verinin mevcut sayfanın altına eklenerek
aktığı modern bir deneyim sunar.
Sona Ekleme Stratejisi ve "beforeend" İster bir "Daha Fazla Yükle" butonu ( Click-to-Load ) kullanın, ister kullanıcı aşağı
indikçe otomatik yüklenen "Sonsuz Kaydırma" ( Infinite Scroll ) kurgulayın; her iki yöntemin de kalbinde
hx-swap="beforeend" stratejisi yatar.
Normalde HTMX içeriği değiştirir (Swap).
Ancak sayfalama işleminde amaç değiştirmek değil, büyütmektir.
Sunucu bir sonraki sayfanın verisini gönderdiğinde, HTMX bu yeni parçayı alır ve mevcut listenin üzerine yazmak yerine, listenin en
altına ( kapanış etiketinden hemen öncesine ) nazikçe ekler.
Bu sayede kullanıcı, yukarıdaki okuduğu içerikleri kaybetmeden, listenin sonsuza kadar uzadığı kesintisiz bir akış deneyimi yaşar.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Pagination</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<div class="list-container">
<h3 style="margin-top:0; color:#333;">📦 Ürün Listesi</h3>
<div id="liste-icerigi">
<div class="item">Veri Satırı 1</div>
<div class="item">Veri Satırı 2</div>
<div class="item">Veri Satırı 3</div>
</div>
<button
id="load-more-btn"
class="btn-load"
hx-get="/api/liste?page=2"
hx-target="this"
hx-swap="outerHTML"
>
Daha Fazla Yükle...
</button>
</div>
<script src="script.js?v=1.0.150"></script>
</body>
</html>
.list-container {
font-family: 'Segoe UI', sans-serif;
max-width: 400px;
margin: 20px auto;
border: 1px solid #ddd;
padding: 10px;
border-radius: 8px;
background-color: #fff;
}
.item {
padding: 15px;
background: #f8fafc;
margin-bottom: 8px;
border-left: 4px solid #3b82f6;
border-radius: 4px;
animation: fadeIn 0.5s ease;
}
.btn-load {
display: block;
width: 100%;
padding: 12px;
background: #3b82f6;
color: white;
text-align: center;
cursor: pointer;
border: none;
border-radius: 6px;
font-weight: 600;
margin-top: 10px;
}
.btn-load:hover {
background: #2563eb;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
const MAX_PAGES = 4;
document.body.addEventListener("htmx:beforeRequest", function (evt) {
if (evt.detail.requestConfig.path.includes("/api/liste")) {
evt.preventDefault();
const params = new URLSearchParams(
evt.detail.requestConfig.path.split("?")[1]
);
const currentPage = parseInt(params.get("page")) || 1;
const nextPage = currentPage + 1;
let responseHTML = "";
// 1. Yeni Veri Satırlarını Oluştur
// Not: Bunları 'liste-icerigi' div'ine append etmiyoruz!
// Butonun yerine geçecek HTML paketini hazırlıyoruz.
// Ancak görsel olarak düzgün durması için script ile yukarı taşıyacağız.
let newItems = "";
for (let i = 1; i <= 3; i++) {
const itemNumber = (currentPage - 1) * 3 + 3 + i;
newItems += `<div class="item">Veri Satırı ${itemNumber}</div>`;
}
let nextButtonHTML = "";
if (currentPage < MAX_PAGES) {
nextButtonHTML = `
<button class="btn-load"
hx-get="/api/liste?page=${nextPage}"
hx-target="this"
hx-swap="outerHTML">
Daha Fazla Yükle...
</button>
`;
} else {
nextButtonHTML = `<div style="text-align:center; padding:15px; color:#94a3b8;">Tüm veriler yüklendi ✅</div>`;
}
// 3. HİLE (Trick):
// HTMX 'outerHTML' ile butonun yerine geçecek.
// Ancak biz yeni verilerin 'liste-icerigi' div'inin içinde olmasını istiyoruz.
// Bu yüzden manuel DOM manipülasyonu yapıp, HTMX'e sadece butonu vereceğiz.
document
.getElementById("liste-icerigi")
.insertAdjacentHTML("beforeend", newItems);
evt.detail.target.outerHTML = nextButtonHTML;
htmx.process(document.body);
}
});
WebSockets & SSE ile Canlı İçerik Gerçek Zamanlı Veri Akışı
Web sayfaları doğası gereği statik ve pasiftir; geleneksel HTTP protokolünde sunucu, kendisine soru sorulmadıkça cevap veremez (Request-Response döngüsü).
Ancak modern web deneyimi; borsa grafiklerinin saniye saniye değiştiği, mesajların anında düştüğü ve bildirimlerin canlı aktığı reaktif bir yapı talep eder.
Bu "anlık" (Real-time) dünyada, verinin istemci tarafından çekilmesi ( Pull ) değil, sunucu tarafından itilmesi ( Push ) gerekir.
HTMX, genellikle karmaşık JavaScript kodları ve harici kütüphaneler (Socket.io vb.) gerektiren bu ileri seviye iletişim protokollerini, HTML'in sadeliğine indirger.
hx-sse ve hx-ws uzantıları (extensions) sayesinde, HTML elementleriniz sunucuyla sürekli açık bir hat üzerinden konuşan canlı bileşenlere dönüşür.
Artık sayfanız sadece yüklenen bir belge değil, sunucuyla senkronize yaşayan bir arayüzdür.
Bu bölümde, HTMX'in bu dünyaya açılan iki kapısı olan Server Sent Events (SSE) ve WebSockets teknolojilerini inceleyeceğiz.
Tek Yönlü İletişim Server Sent Events (SSE)
İletişim tek yönlüdür; sunucu yayın yapar, tarayıcı sadece dinler.
HTTP protokolünün standart bir parçası olduğu için güvenlik duvarları ( Firewall ) veya özel sunucu konfigürasyonları ile uğraşmanıza gerek kalmaz.
Mekanizma ve Kullanım Felsefesi:HTMX'in hx-sse uzantısı, tarayıcıda bir dinleme kanalı (EventSource) açar.
Sunucu, bu kanal üzerinden belirli aralıklarla veya bir olay gerçekleştiğinde ("Gol Oldu!" gibi) metin tabanlı mesajlar gönderir.
HTMX, bu mesajları yakalar ve sanki bir AJAX yanıtıymış gibi işleyerek HTML içeriğini günceller (Swap).
Neden ve Ne Zaman Kullanılır?Eğer uygulamanızda verinin kaynağı sadece sunucuysa ve kullanıcının bu veriye anlık bir cevap vermesi gerekmiyorsa
( sadece izliyorsa ), SSE en doğru ve hafif çözümdür.
Canlı Haber Akışları:Son dakika gelişmelerini sayfayı yenilemeden ekrana düşürmek.
Sistem Durum Panelleri:CPU kullanımı, bellek doluluğu gibi metrikleri saniye saniye güncellemek.
Bildirimler:"Siparişiniz kargoya verildi" gibi uyarıları anında göstermek.
Tek Yönlü İletişim Sunucudan Canlı Yayın
Çift Yönlü İletişim WebSockets
Eğer SSE radyo yayınıysa, WebSockets bir "Telefon Görüşmesi"dir.
İletişim çift yönlüdür (Full-Duplex); hem sunucu hem de tarayıcı aynı anda konuşabilir, birbirini dinleyebilir ve veri gönderebilir.
Bu, HTTP protokolünün bir üst seviyeye (Upgrade) taşınmış halidir ve sürekli açık bir TCP tüneli oluşturur.
Mekanizma ve Kullanım Felsefesi:HTMX'in hx-ws uzantısı, bu karmaşık protokolü standart HTML formları ve elementleriyle yönetmenizi sağlar.
Bir WebSocket bağlantısı kurulduğunda (ws-connect), HTMX bu kanalı dinlemeye başlar.
Sunucudan gelen HTML parçalarını sayfaya yerleştirir.
Daha da önemlisi, sayfa üzerindeki form gönderimlerini (ws-send) yakalar ve bunları standart HTTP POST isteği yapmak yerine, açık olan WebSocket tünelinden yıldırım hızıyla sunucuya fırlatır.
Neden ve Ne Zaman Kullanılır?Eğer uygulamanızda "Diyalog" varsa, WebSockets zorunluluktur.
Sohbet Uygulamaları (Chat):Yazılan mesajın anında gitmesi ve karşı tarafın "Yazıyor..." bilgisinin anında gelmesi.
Ortak Çalışma Araçları:Google Docs veya Figma gibi, birden fazla kişinin aynı anda aynı veriyi düzenlediği ekranlar.
Etkileşimli Oyunlar:Sunucunun ve oyuncunun milisaniyeler içinde haberleşmesi gereken durumlar.
Çift Yönlü İletişim WebSocket Tüneli
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX SSE Örneği</title>
<link rel="stylesheet" href="">
</head>
<body>
<div class="ticker-card" hx-ext="sse" sse-connect="/api/canli-piyasa">
<div class="ticker-header">
<span class="coin-name">Bitcoin (BTC)</span>
<span class="live-badge">Canlı</span>
</div>
<div sse-swap="fiyat-guncellemesi">
<div class="price-display">$42,500.00</div>
<div class="price-change">+1.2% (Son 24s)</div>
</div>
</div>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script src="https://unpkg.com/htmx.org@1.9.10/dist/ext/sse.js"></script>
</body>
</html>
.ticker-card {
font-family: 'Segoe UI', sans-serif;
background: #1e293b;
color: white;
padding: 20px;
border-radius: 12px;
width: 300px;
margin: 20px auto;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
border: 1px solid #334155;
}
.ticker-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
border-bottom: 1px solid #334155;
padding-bottom: 10px;
}
.coin-name {
font-weight: bold;
font-size: 1.1rem;
}
.live-badge {
background-color: #ef4444;
color: white;
font-size: 0.7rem;
padding: 2px 8px;
border-radius: 10px;
text-transform: uppercase;
animation: pulse 2s infinite;
}
.price-display {
font-size: 2rem;
font-weight: 700;
color: #34d399;
/* Yeşil */
}
.price-change {
font-size: 0.9rem;
color: #94a3b8;
margin-top: 5px;
}
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX SSE Örneği</title>
<link rel="stylesheet" href="">
</head>
<body>
<div class="ticker-card" hx-ext="sse" sse-connect="/api/canli-piyasa">
<div class="ticker-header">
<span class="coin-name">Bitcoin (BTC)</span>
<span class="live-badge">Canlı</span>
</div>
<div sse-swap="fiyat-guncellemesi">
<div class="price-display">$42,500.00</div>
<div class="price-change">+1.2% (Son 24s)</div>
</div>
</div>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script src="https://unpkg.com/htmx.org@1.9.10/dist/ext/sse.js"></script>
</body>
</html>
.chat-wrapper {
font-family: 'Segoe UI', sans-serif;
max-width: 400px;
margin: 20px auto;
border: 1px solid #ddd;
border-radius: 12px;
overflow: hidden;
background: #fff;
}
.chat-header {
background-color: #0ea5e9;
color: white;
padding: 15px;
font-weight: bold;
display: flex;
align-items: center;
gap: 10px;
}
.chat-messages {
height: 300px;
padding: 20px;
background-color: #f8fafc;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
.msg {
padding: 8px 12px;
border-radius: 8px;
max-width: 70%;
font-size: 0.9rem;
}
.msg-in {
background: #e2e8f0;
align-self: flex-start;
}
.msg-out {
background: #0ea5e9;
color: white;
align-self: flex-end;
}
.chat-input-area {
padding: 15px;
border-top: 1px solid #eee;
display: flex;
gap: 10px;
}
.chat-input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
outline: none;
}
.btn-send {
background: #0ea5e9;
color: white;
border: none;
padding: 10px 20px;
border-radius: 20px;
cursor: pointer;
font-weight: bold;
}
Advanced (İleri Seviye) Özellikler HTMX'in Sınırlarını Zorlamak
Bu noktaya kadar HTMX ile sayfa içindeki parçaları değiştirmeyi, formları yönetmeyi ve sunucu ile canlı diyalog kurmayı öğrendik.
Bunlar, dinamik bir arayüz için güçlü temellerdir.
Ancak modern bir yazılım projesini, basit bir "Web Sitesi"nden ayıran ve onu tam donanımlı bir "Web Uygulaması" ( Web App ) yapan daha derin katmanlar vardır.
Kullanıcılar artık sadece tıklayınca değişen içerikler değil; tarayıcının geri butonuna bastığında doğru yere giden, sayfa geçişlerinde göz kırpmayan ( flicker-free), güvenliği (CSRF) sağlanmış ve istemci tarafındaki karmaşık mantıklarla (Alpine.js vb.) uyum içinde çalışan profesyonel sistemler bekliyor.
Profesyonel Geliştiricinin Araç Çantası Bu bölümde, HTMX'i sadece bir "AJAX kütüphanesi" olarak kullanmanın ötesine geçeceğiz.
Navigasyon Deneyimi:Tarayıcı geçmişini (History API) ve URL çubuğunu, sanki bir SPA (Single Page App) kullanıyormuşçasına pürüzsüz yönetmeyi,
Performans Optimizasyonu:hx-boost ile tüm siteyi tek bir hamlede "turboşarjlı" hale getirmeyi , hx-swap-o ile sayfa geçişlerinde göz kırpmayan (flicker-free), güvenliği (CSRF) sağlanmış ve istemci tarafındaki karmaşık mantıklarla (Alpine.js vb.) uyum içinde çalışan profesyonel sistemler bekliyor.
Entegrasyon ve Güvenlik:Modern frontend ekosistemiyle ve backend güvenlik protokolleriyle nasıl dans edileceğini inceleyeceğiz.
Boosted Links (hx-boost) Tek Satırda SPA (Single Page App) Deneyimi
Modern web geliştirmede en büyük ikilem şudur: Ya geleneksel (MPA) yöntemle yapıp her tıklamada sayfanın yenilendiği "titrek" bir deneyim sunarsınız ya da React/Next.js gibi framework'lere geçip "Client-Side Routing" ile pürüzsüz ama karmaşık bir mimari kurarsınız.
HTMX, hx-boost özniteliği ile üçüncü bir yol sunar: Mevcut, klasik web sitenizi tek bir satır kodla, SPA (Tek Sayfa Uygulama) hızına ve hissiyatına kavuşturmak.
Bu özellik, "Progressive Enhancement" (Aşamalı İyileştirme) felsefesinin zirvesidir.
Sitenizin mimarisini, linklerini veya formlarını değiştirmeden; tarayıcının varsayılan navigasyon davranışını "turboşarjlı" bir versiyona yükseltir.
Nasıl Çalışır? Mekanik Analizhx-boost="true" özelliğini genellikle en tepeye, <body> etiketine veya ana içerik kapsayıcısına verirsiniz. HTMX, bu alandaki tüm standart linkleri (<a href="..."></a>) ve formları (<form action="..."></form>) tarar ve onları "ele geçirir".
Tek satırda SPA (Single Page Application) deneyimi için süreç şöyle işler:
Yakalama (Interception):Kullanıcı normal bir linke tıkladığında, HTMX tarayıcının standart "sayfayı git ve yükle" emrini durdurur (preventDefault).
Arka Plan İsteği:Tıklanan adrese arka planda bir AJAX (XHR) isteği gönderir. Kullanıcı bu sırada mevcut sayfada kalmaya devam eder.
Akıllı Ayrıştırma (Parsing):Sunucu normal bir HTML sayfası döndürür.
HTMX bu cevabı alır, <body> etiketinin içini çekip çıkarır.
Cerrahi Değişim (Swap):Mevcut sayfanın <body> içeriğini siler ve gelen yeni içeriği oraya koyar.
<head> kısmındaki scriptler ve stiller (eğer değişmediyse) korunur.
Tarihçe Yönetimi:En önemlisi, tarayıcının adres çubuğunu (URL) yeni gidilen adresle günceller ve tarayıcı geçmişine (History API) ekler.
Böylece "Geri" butonu beklendiği gibi çalışır.
Sonuç:Algısal Hız ve Konfor Kullanıcı deneyimi açısından sonuç büyüleyicidir.
Sayfa asla beyazlamaz (White Flash), CSS ve JavaScript dosyaları tekrar tekrar indirilip derlenmez (Re-evaluation).
Kullanıcı sanki masaüstü bir uygulama kullanıyormuş gibi akıcı geçişler yaşar; oysa altyapı hala bildiğimiz, basit HTML sayfalarıdır.
Bu özellik, Turbolinks veya Swup.js gibi kütüphanelerin yaptığı işin aynısını, HTMX'in yerleşik bir yeteneği olarak sunar.
SPA Deneyimi Akışı hx-boost ile Tek Satırda
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Boost Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body hx-boost="true">
<nav class="navbar">
<a href="/anasayfa" class="nav-link">Ana Sayfa</a>
<a href="/hakkimizda" class="nav-link">Hakkımızda</a>
<a href="/iletisim" class="nav-link">İletişim</a>
</nav>
<main id="ana-icerik">
<h1>Hoş Geldiniz</h1>
<p>
Yukarıdaki linklere tıkladığınızda sayfa <strong>yenilenmez (refresh olmaz)</strong>.
HTMX arka planda sayfayı getirir ve sadece 'body' içeriğini değiştirir.
</p>
<p>Tarayıcının <strong>Geri/İleri</strong> butonları da kusursuz çalışır.</p>
</main>
</body>
</html>
body {
font-family: 'Segoe UI', sans-serif;
margin: 0;
padding: 0;
background: #f8fafc;
}
.navbar {
background: #1e293b;
padding: 15px 30px;
display: flex;
gap: 20px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.nav-link {
color: #cbd5e1;
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
}
.nav-link:hover {
color: #fff;
}
#ana-icerik {
padding: 40px;
max-width: 800px;
margin: 0 auto;
background: white;
min-height: 300px;
margin-top: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
Push-state / Browser History Yönetimi URL Çubuğunu Yönetmek ve Derin Linkleme
AJAX ile çalışan dinamik web sitelerinin (SPA) en büyük handikapı şudur: Ekranda içerik değişir, sayfalar akar gider ama tarayıcının adres çubuğundaki URL hep aynı kalır (örneğin sadece /anasayfa).
Bu durum iki büyük sorun yaratır:
Paylaşılamazlık:Kullanıcı filtreleyip bulduğu "Kırmızı Spor Ayakkabı" sayfasının linkini arkadaşına atarsa, arkadaşı sadece "Anasayfa"yı görür.
Geri Butonu Sorunu:Kullanıcı 5 farklı içeriğe tıkladıktan sonra "Geri" butonuna basarsa, önceki içeriğe değil, sitenin dışına (Google'a) atılır.
HTMX Çözümü: İz Bırakarak İlerlemek (hx-push-url)HTMX, hx-push-url="true" özelliği ile HTML5 History API'yi otomatik olarak yönetir.
Siz ekstra JavaScript kodu yazmadan, HTMX tarayıcıya şunu fısıldar: "Evet, sayfayı tamamen yenilemedim ama içerik değişti , lütfen adres çubuğunu güncelle ve geçmişe kaydet." cevabını verir.
Süreç Nasıl İşler? Adım Adım Navigasyonhx-push-url mekanizması, kullanıcının hissettiği ile tarayıcının kaydettiği gerçekliği senkronize eden bir köprüdür.
Bir etkileşim sırasında arka planda şu dört kritik aşama gerçekleşir:
Eylem (Action):Kullanıcı, örneğin bir ürün listesindeki "Detay" butonuna tıklar veya ayarlar menüsünde "Profil" sekmesine geçer.
Bu, yolculuğun başlangıç noktasıdır.
Eylem (Action): Değişim (Swap):HTMX, bu isteği alır ve sunucuyla konuşur. Sunucudan dönen yeni HTML parçası (örneğin ürünün detay kartı), sayfadaki listenin yerini alır.
Kullanıcı artık yeni içeriği görmektedir; ancak teknik olarak hala "eski" sayfadadır.
Tarihçe Kaydı (History Push):İşte sihir burada gerçekleşir, HTMX, içerik değiştiği anda tarayıcıya müdahale eder ve "Adres çubuğundaki URL'i, az önce gittiğim adresle (örneğin /urun/42) güncelle ve bunu geçmişe bir çentik olarak at" der ve sayfa yenilenmez, ama adres değişir.
Sonuç (Persistence):Artık kullanıcı bu linki kopyalayıp arkadaşına gönderebilir veya sayfayı yenileyebilir (F5). Tarayıcı o anki URL'i bildiği için, kullanıcıyı ana sayfaya değil, doğrudan o ürünün detayına götürür.
Ayrıca tarayıcının "Geri" butonuna basıldığında, HTMX geçmişten bir önceki durumu çağırır ve kullanıcıyı başladığı listeye geri döndürür.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Push URL Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<h3>⚙️ Ayarlar</h3>
<div class="nav-tabs">
<button
class="nav-btn"
hx-get="/api/ayarlar/genel"
hx-target="#panel-icerigi"
hx-push-url="true"
>
Genel
</button>
<button
class="nav-btn"
hx-get="/api/ayarlar/profil"
hx-target="#panel-icerigi"
hx-push-url="true"
>
Profil
</button>
<button
class="nav-btn"
hx-get="/api/ayarlar/gizlilik"
hx-target="#panel-icerigi"
hx-push-url="true"
>
Gizlilik
</button>
</div>
<div id="panel-icerigi">
<p>Lütfen yukarıdan bir ayar menüsü seçiniz.</p>
</div>
</body>
</html>
.nav-tabs {
border-bottom: 2px solid #ddd;
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.nav-btn {
padding: 10px 20px;
border: none;
background: none;
cursor: pointer;
font-size: 1rem;
color: #555;
border-bottom: 3px solid transparent;
transition: all 0.3s;
}
.nav-btn:hover {
background-color: #f9f9f9;
}
.nav-btn.active {
color: #2563eb;
border-bottom-color: #2563eb;
}
#panel-icerigi {
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
background: #fff;
}
Güvenlik (CSRF Token Geçme) Modern Web Güvenliği ile Dans
Django, Laravel, Rails, Spring Boot veya ASP.NET Core gibi modern backend framework'leri, uygulamaları kötü niyetli saldırılara karşı korumak için CSRF ( Cross-Site Request Forgery ) mekanizmasını zorunlu tutar.
Bu güvenlik protokolü, sunucunun gelen isteğin gerçekten kendi güvenilir kullanıcısından geldiğini doğrulaması için her işleme özel bir "imza" ( Token ) ister.
Geleneksel Sorun:Standart HTML formlarında bu token, formun içine gizli bir kutucuk ( <input type="hidden" name="_token"> ) olarak eklenir.
Ancak HTMX ile her butona, her inputa bu gizli alanı eklemek hem kod kirliliği yaratır hem de yönetimi zordur.
HTMX Çözümü:Global Başlık Yönetimi HTMX, bu sorunu hx-headers özniteliği ve miras alma (inheritance) yeteneği ile zarifçe çözer.
Token'ı her isteğe tek tek eklemek yerine; sayfanın en tepesinde (örneğin body etiketinde) bir kural tanımlarsınız.
HTMX, bu kuralı o sayfa içindeki tüm alt elementlere (butonlar, formlar) otomatik olarak uygular.
Böylece güvenlik, arayüz kodunu kirletmeden arka planda sessizce işler.
En Temiz Yöntem (Meta Tag Stratejisi):Backend geliştiricisi, güvenlik jetonunu sayfanın <head> kısmına bir meta etiketi olarak basar.
HTMX ise bu etiketi okuyarak, yaptığı her POST, PUT, DELETE isteğinin başlığına (Header) bu jetonu otomatik olarak ekler.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX CSRF Güvenlik Örneği</title>
<meta name="csrf-token" content="GENERATED_SECURE_TOKEN_XYZ_999">
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body hx-headers='{"X-CSRF-Token": "meta[name=csrf-token]"}'>
<div class="security-box">
<h3>🛡️ Güvenli İşlem Alanı</h3>
<p>
Aşağıdaki butona bastığınızda, HTMX otomatik olarak
<code><head></code> içindeki token'ı okur ve isteğin başlık (header) kısmına ekler.
</p>
<button
class="btn-danger"
hx-post="/api/hesap/sil"
hx-confirm="Bu işlem geri alınamaz. Emin misiniz?"
hx-swap="outerHTML">
Kritik İşlem Yap
</button>
</div>
</body>
</html>
body {
font-family: 'Segoe UI', sans-serif;
background-color: #f8f9fa;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.security-box {
background-color: #ffffff;
border: 1px solid #e2e8f0;
border-top: 4px solid #e74c3c;
border-radius: 8px;
padding: 30px;
max-width: 450px;
text-align: center;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
h3 {
color: #2d3748;
margin-top: 0;
}
p {
color: #718096;
line-height: 1.5;
margin-bottom: 25px;
}
code {
background-color: #edf2f7;
padding: 2px 6px;
border-radius: 4px;
color: #c53030;
font-family: monospace;
font-size: 0.9em;
}
.btn-danger {
background-color: #e74c3c;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease, transform 0.1s ease;
font-size: 1rem;
}
.btn-danger:hover {
background-color: #c0392b;
}
.btn-danger:active {
transform: scale(0.98);
}
Modern Ekosistemle Entegrasyon Uyumlu ve Hibrit Mimari
Yazılım geliştirme dünyasında sıkça düşülen en büyük yanılgı, "gümüş kurşun" arayışıdır; yani tek bir kütüphanenin veya framework'ün, projenin veritabanından animasyonuna kadar her sorununu çözeceğine inanmak.
Oysa modern web'in karmaşıklığı, tek bir aracın yönetebileceğinden çok daha fazladır.
HTMX, sunucu ile istemci arasındaki HTML trafiğini yönetmekte, veriyi taşımakta ve DOM'u güncellemekte tartışmasız bir ustadır.
Ancak bir web uygulamasının ihtiyaçları sadece veri transferi ile sınırlı değildir; çevrimdışı çalışma yetenekleri, milisaniyelik arayüz (UI) animasyonları veya sunucuya gitmeyi gerektirmeyen anlık istemci mantıkları, bazen HTMX'in sınırlarını aşabilir.
İşte tam bu noktada HTMX'in, rakiplerinden ayrılan gerçek süper gücü ortaya çıkar: Uyum ve Birlikte Çalışabilirlik.
"Totaliter" Rejimlere Karşı "Barışçıl" Diplomasi React, Angular veya Vue gibi modern framework'ler, tarayıcının doğal DOM yapısını yok sayarak kendi "Sanal DOM" (Virtual DOM) krallıklarını kurarlar.
Bu "Totaliter" yaklaşım, o ekosistemin dışındaki herhangi bir kütüphaneyi kullanmayı zorlaştırır; her şeyi o framework'ün kurallarına göre yeniden yazmanız gerekir.
HTMX ise tam tersine, standartlara sadık ve demokratik bir yaklaşım sergiler.
Tarayıcının doğal olaylarını (Events) ve gerçek DOM yapısını kullandığı için, ekosistemdeki diğer oyuncularla "Barışçıl Birliktelik" (Peaceful Coexistence) içinde yaşar. O, ne diğer kütüphaneleri engeller ne de onlardan etkilenir.
Görev Dağılımı ve Sinerji Bu hibrit mimaride HTMX, uygulamanızın ağır işçiliğini yani Sunucu İletişimini (Backend Communication) üstlenirken, diğer uzmanlık alanlarını o işin ustalarına bırakır:
Arayüz Etkileşimleri (Alpine.js):Bir menüyü açıp kapatmak (toggle), bir modalı göstermek veya karakter sayacı yapmak için sunucuya gitmeye gerek yoktur.
Bu "mikro etkileşimler" için HTMX, sahneyi Alpine.js gibi hafif ve deklaratif kütüphanelere bırakır.
Çevrimdışı Yetenekler (PWA):İnternet kesildiğinde uygulamanın çalışmaya devam etmesi, önbellekleme ve "Uygulama Kabuğu" (App Shell) yönetimi için modern Service Worker teknolojilerine alan açar.
Stil ve Görünüm (Tailwind CSS):HTMX'in "Davranışın Yerelliği" (Locality of Behavior) ilkesi, Tailwind'in "Stilin Yerelliği" ilkesiyle kusursuz bir uyum sağlar.
İkisi de HTML'den ayrılmadan geliştirme yapmanıza olanak tanır.
Bu bölümde, HTMX'i modern web standartlarıyla harmanlayıp, "her iş için en doğru aracı" (Best Tool for the Job) kullanma prensibiyle; nasıl bakımı kolay, performansı yüksek, esnek ve geleceğe dönük hibrit mimariler kurabileceğimizi detaylıca inceleyeceğiz.
Progressive Web App (PWA) Yaklaşımı Çevrimdışı Dünyada HTML'in Gücü ve "Uygulama Kabuğu" Stratejisi
Modern web'in en büyük vaatlerinden biri, web sitelerinin internet kesildiğinde bile çalışmaya devam edebilmesi, yani birer
"Progressive Web App" (PWA) gibi davranabilmesidir.
Geleneksel SPA (React/Angular) dünyasında bu yapıyı kurmak, genellikle devasa JavaScript paketlerini ("bundle") indirmeyi, karmaşık veri senkronizasyon algoritmalarını ve istemci tarafı veritabanlarını yönetmeyi gerektirir.
HTMX ise bu karmaşık denklemi, HTML'in doğal hafifliği ile tersine çevirir.
HTMX sayfaları doğası gereği minimalisttir; JSON verisi ve onu işleyecek render motoru yerine, doğrudan nihai sonucu (HTML) taşırlar.
Bu hafiflik, Service Worker teknolojisi ile birleştiğinde muazzam bir avantaja dönüşür.
Service Worker, tarayıcı ile ağ arasına giren akıllı bir vekil sunucu ( proxy ) gibi davranarak, sunucudan gelen HTML parçalarını anında önbelleğe ( Cache ) alır.
İnternet bağlantısı koptuğunda, HTMX sunucuya ulaşamasa bile, Service Worker devreye girerek kullanıcının daha önce gezdiği sayfaları veya uygulamanın iskeletini (App Shell) yerel hafızadan milisaniyeler içinde sunar.
Basitleştirilmiş "App Shell" Modeli PWA mimarisinin temeli olan "Uygulama Kabuğu" ( App Shell ); başlık, menü ve alt bilgi gibi sabit kısımların anında yüklenmesi, içeriğin ise sonradan gelmesi prensibine dayanır.
HTMX ile bu yapıyı kurmak için karmaşık "Hydration" süreçlerine ihtiyacınız yoktur.
Kullanıcı uygulamayı açtığında bu kabuk anında ekrana gelir.
Service Worker sadece temel CSS ve ana HTML şablonunu önbellekler.
Ardından HTMX, hx-trigger="load" gibi basit bir tetikleyici ile canlı içeriği sunucudan (veya varsa önbellekten) talep eder.
Bu yaklaşım, gigabytlarca veri harcamadan, düşük işlem gücüne sahip mobil cihazlarda bile "Native App" (Yerel Uygulama) performansında çalışan, dayanıklı ve erişilebilir web deneyimleri inşa etmenizi sağlar.
HTMX + Alpine.js: Mükemmel İkili Sunucu ve İstemci Arasındaki Görev Dağılımı: Modern "jQuery" Ruhu
HTMX, sunucu ile konuşma ve veri alışverişi konusunda bir uzman olsa da, tarayıcı içinde gerçekleşen anlık ve sunucu bağımsız "mikro etkileşimler" ( Micro-interactions ) konusunda bazen ağır kalabilir.
Örneğin:Bir açılır menüyü (Dropdown) göstermek veya bir modal penceresini kapatmak için sunucuya istek göndermek verimsizdir ve işte bu noktada sahneye Alpine.js çıkar.
Bu iki kütüphane, felsefi olarak aynı DNA'yı paylaşır: İkisi de JavaScript dosyaları içinde kaybolmak yerine, doğrudan HTML etiketleri üzerine yazılan ( Deklaratif ) bir yapı sunar.
Ancak görev sahaları kesin çizgilerle ayrılmıştır:
Veri Durumu (Data State) ve Arayüz Durumu (UI State) Bu mükemmel uyumun sırrı, "State" (Durum) kavramını ikiye bölmelerinde yatar.
HTMX (Veri Uzmanı):Uygulamanın "Kalıcı Verilerini" yönetir daha sonra veritabanından listeyi çeker, formu kaydeder, kullanıcıyı siler ve onun muhatabı sunucudur ( Backend ).
Alpine.js (Arayüz Uzmanı):Uygulamanın "Geçici Arayüz Durumunu" yönetir.
Menü açık mı? Checkbox işaretli mi? Modal görünür mü? Onun muhatabı sadece tarayıcıdır (Frontend).
Bu iş birliği, 2010'ların başındaki "jQuery" döneminin sağladığı o muazzam esnekliği ve gücü, modern web standartlarıyla ve çok daha temiz bir kod yapısıyla geri getirir.
Geliştirici, React veya Vue gibi ağır framework'lerin karmaşıklığına girmeden; biriyle veriyi taşıyıp diğeriyle arayüzü canlandırarak, son derece hafif, hızlı ve bakımı kolay "Hibrit" uygulamalar geliştirebilir.
Backend Entegrasyonu (Dil Bağımsızlık) Özgürleşen Sunucu Mimarisi: "Bildiğin Dil, En İyi Dildir"
Modern frontend dünyasında ( React, Angular, Vue ) geliştirme yapabilmek için genellikle backend tarafında da Node.js ekosistemine veya çok spesifik JSON serileştirme kütüphanelerine bağımlı kalırsınız.
HTMX, bu zincirleri kırar ve sunucu tarafındaki teknoloji seçimini tamamen geliştiricinin tercihine bırakır.
HTMX'in sunucuyla yaptığı anlaşma tek bir cümleliktir: "Bana JSON veri yığını değil, işlenmiş HTML parçası gönder." Sunucunuzun bir Python scripti, bir Go binary'si, bir PHP sayfası veya hatta bir C++ programı olması HTMX'in umurunda değildir.
HTTP üzerinden HTML metni ( string ) gönderebilen her şey, HTMX için mükemmel bir backend'dir.
Bu yaklaşım, ekiplerin yeni diller öğrenmek zorunda kalmadan, uzman oldukları teknolojilerle modern ve reaktif arayüzler geliştirmesine olanak tanır.
Teknolojilere Göre HTMX Yaklaşımı: Go (Templ & HTML Templates):Performansın kritik olduğu projelerde Go ve HTMX muazzam bir ikilidir.
Go'nun "Type-Safe" (Tip Güvenli) şablon motorları ( Templ ), HTML hatalarını daha derleme ( compile ) aşamasında yakalamanızı sağlar.
Sonuç; ışık hızında çalışan ve hata yapması zor bir backend mimarisidir.
Python (Django / Jinja2):Django ve Flask gibi framework'ler, doğuştan "Server-Side Rendering" için tasarlanmıştır. HTMX, bu framework'lerin zaten çok güçlü olan şablon (Template) yeteneklerini yeniden parlatır.
Veritabanından veriyi çekip Jinja2 ile HTML'e basmak, JSON API yazmaktan çok daha hızlı ve doğaldır.
Node.js (Express + EJS/Pug): JavaScript ekosisteminden kopmak istemeyenler için de HTMX bir avantajdır.
Backend'de Express.js kullanıp, frontend'de React karmaşasına girmeden; EJS veya Pug gibi şablon motorlarıyla çok hızlı prototipleme yapabilir ve full-stack projeler üretebilirsiniz.
PHP (Laravel / Blade):Web'in orijinal dili PHP, HTMX felsefesine en yakın dildir.
Laravel'in Blade motoru, HTML parçalarını (Components/Fragments) yönetmek için harika araçlar sunar.
Klasik PHP gücünü, modern SPA hissiyatıyla birleştirmek için en olgun ve kararlı yoldur.
Out of Band Swap Mimarisi Tek İstekle Çoklu Güncelleme (Zincirleme Reaksiyon Yönetimi)
Standart bir HTMX akışında, felsefe basittir: "Bir istek gönder, bir cevap al ve tek bir hedefi (hx-target) güncelle." Bu, basit etkileşimler için mükemmel çalışır.
Ancak gerçek dünyadaki, karmaşık ve modern web uygulamalarında bir kullanıcının yaptığı tek bir eylem (bir "Satın Al" butonuna tıklaması), sayfanın birbirinden tamamen uzak köşelerinde eş zamanlı değişiklikler, yani yazılım dilinde "Yan Etkiler" (Side Effects) gerektirebilir.
Senaryo:Bir blog yazısına yorum yapıldığını düşünelim.
Bu işlem gerçekleştiğinde aynı anda 3 şey olmalıdır:
Yeni yorum, yorum listesinin en altına eklenmelidir (Ana Hedef).
VE sayfanın en tepesindeki "Toplam Yorum Sayısı" sayacı artmalıdır (Yan Hedef).
VE kullanıcının yazı yazdığı form kutucuğu temizlenmelidir (Yan Hedef).
OOB (Bant Dışı) Çözümü:HTMX, bu durumu yönetmek için ek JavaScript kodlarına veya karmaşık durum yönetimi (State Management) kütüphanelerine ihtiyaç duymaz.
Çözüm, sunucudan dönen HTML paketinin içine "gizli yolcular" eklemektir. HTMX, sunucudan gelen cevabın içinde, ana hedeften tamamen bağımsız ekstra HTML parçacıkları "sıkıştırmanıza" izin verir.
Eğer gelen cevaptaki bir elementin üzerinde hx-swap-oob="true" özniteliği ve belirgin bir id değeri varsa; HTMX bu parçayı ana hedefe koymaz.
Bunun yerine sayfayı tarar, o id'ye sahip olan elementi bulur ve onu gelen bu yeni sürümle otomatik olarak değiştirir.
Bu sayede tek bir HTTP isteği ile sayfanın 5 farklı yerini aynı anda güncelleyebilirsiniz.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX OOB Sepet Örneği</title>
<link rel="stylesheet" href="style.css?v=1.0.150">
</head>
<body>
<nav class="navbar">
<div class="brand">⚡ HTMX Market</div>
<div id="sepet-sayaci" class="cart-badge-container">
🛒 Sepet: <span>0 Ürün</span>
</div>
</nav>
<div class="main-container">
<div class="products-grid">
<div class="product-card">
<div class="product-emoji">🥼</div>
<div class="product-title">T-Shirt</div>
<div class="product-price">1.200 TL</div>
<button class="btn-add"
hx-post="/api/sepete-ekle"
hx-vals='{"id": 1, "fiyat": 1200}'
hx-target="this"
hx-swap="outerHTML">
Sepete Ekle
</button>
</div>
<div class="product-card">
<div class="product-emoji">⌚</div>
<div class="product-title">Akıllı Saat</div>
<div class="product-price">3.500 TL</div>
<button class="btn-add"
hx-post="/api/sepete-ekle"
hx-vals='{"id": 2, "fiyat": 3500}'
hx-target="this"
hx-swap="outerHTML">
Sepete Ekle
</button>
</div>
</div>
<div class="cart-summary">
<div class="cart-title">Sipariş Özeti</div>
<div id="sepet-detay">
<div class="empty-cart">Sepetiniz henüz boş.</div>
</div>
<div style="margin-top: 20px; font-weight: bold; text-align: right;">
Toplam: <span id="toplam-tutar">0</span> TL
</div>
</div>
</div>
</body>
</html>
:root {
--primary: #4f46e5;
--primary-hover: #4338ca;
--bg-color: #f9fafb;
--text-main: #1f2937;
--text-light: #6b7280;
--white: #ffffff;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background-color: var(--bg-color);
color: var(--text-main);
margin: 0;
padding: 0;
}
.navbar {
background-color: var(--white);
padding: 1rem 2rem;
box-shadow: var(--shadow);
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 10;
}
.brand {
font-size: 1.25rem;
font-weight: 800;
color: var(--primary);
display: flex;
align-items: center;
gap: 0.5rem;
}
.cart-badge-container {
background-color: #eff6ff;
color: var(--primary);
padding: 0.5rem 1rem;
border-radius: 9999px;
font-weight: 600;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
border: 1px solid #bfdbfe;
}
.main-container {
max-width: 1000px;
margin: 2rem auto;
padding: 0 1rem;
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
}
.product-card {
background-color: var(--white);
border-radius: 1rem;
padding: 1.5rem;
box-shadow: var(--shadow);
transition: transform 0.2s, box-shadow 0.2s;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.product-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
.product-emoji {
font-size: 3rem;
margin-bottom: 1rem;
background: #f3f4f6;
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.product-title {
font-size: 1.1rem;
font-weight: 700;
margin: 0.5rem 0;
}
.product-price {
color: var(--text-light);
margin-bottom: 1.5rem;
font-size: 0.95rem;
}
.btn-add {
background-color: var(--primary);
color: var(--white);
border: none;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
font-weight: 600;
cursor: pointer;
width: 100%;
transition: background-color 0.2s;
}
.btn-add:hover {
background-color: var(--primary-hover);
}
.htmx-request .btn-add {
opacity: 0.7;
cursor: wait;
content: "Ekleniyor...";
}
.cart-summary {
background-color: var(--white);
border-radius: 1rem;
padding: 1.5rem;
box-shadow: var(--shadow);
height: fit-content;
}
.cart-title {
font-size: 1.1rem;
font-weight: 700;
border-bottom: 2px solid #f3f4f6;
padding-bottom: 1rem;
margin-bottom: 1rem;
}
.empty-cart {
color: #9ca3af;
text-align: center;
padding: 2rem 0;
font-style: italic;
}
@media (max-width: 768px) {
.main-container {
grid-template-columns: 1fr;
}
}
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX Dropdown Menü</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Ayarlar</h1>
<p class="subtitle">Hesap tercihlerinizi yönetin.</p>
<div class="dropdown-container">
<button
class="dropdown-trigger"
hx-get="/api/menu-icerik"
hx-target="#menu-content"
hx-swap="innerHTML"
onclick="toggleMenu()"
>
<span>Hesap İşlemleri</span>
<span class="arrow">▼</span>
</button>
<div class="dropdown-menu" id="dropdown-menu">
<div id="menu-content">
<div class="loading-state">
<span class="spinner"></span> Yükleniyor...
</div>
</div>
</div>
</div>
</div>
<div id="toast" class="toast">İşlem başarılı</div>
<script src="script.js"></script>
</body>
</html>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f8fafc;
color: #334155;
min-height: 100vh;
display: flex;
justify-content: center;
padding-top: 100px;
}
.container {
width: 100%;
max-width: 350px;
text-align: center;
}
h1 {
font-size: 1.5rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 0.5rem;
}
p.subtitle {
color: #64748b;
font-size: 0.95rem;
margin-bottom: 2rem;
}
.dropdown-container {
position: relative;
display: inline-block;
width: 100%;
}
.dropdown-trigger {
width: 100%;
background-color: #ffffff;
color: #334155;
border: 1px solid #e2e8f0;
padding: 12px 20px;
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.dropdown-trigger:hover {
background-color: #f1f5f9;
border-color: #cbd5e1;
}
.dropdown-trigger.active {
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
color: #4f46e5;
}
.arrow {
font-size: 0.8rem;
color: #94a3b8;
transition: transform 0.2s ease;
}
.dropdown-trigger.active .arrow {
transform: rotate(180deg);
color: #4f46e5;
}
.dropdown-trigger.loading {
cursor: wait;
opacity: 0.7;
}
.dropdown-menu {
position: absolute;
top: calc(100% + 8px);
left: 0;
width: 100%;
background: white;
border: 1px solid #e2e8f0;
border-radius: 12px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
overflow: hidden;
z-index: 50;
opacity: 0;
transform: translateY(-10px) scale(0.98);
visibility: hidden;
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.dropdown-menu.show {
opacity: 1;
transform: translateY(0) scale(1);
visibility: visible;
}
.loading-state {
padding: 20px;
color: #94a3b8;
font-size: 0.9rem;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.spinner {
width: 16px;
height: 16px;
border: 2px solid #e2e8f0;
border-top-color: #6366f1;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.menu-item {
padding: 12px 16px;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: background 0.1s;
text-decoration: none;
color: #334155;
border-bottom: 1px solid #f1f5f9;
font-size: 0.95rem;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-item:hover {
background-color: #f8fafc;
color: #4f46e5;
}
.menu-icon {
width: 20px;
text-align: center;
color: #64748b;
}
.toast {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%) translateY(20px);
background-color: #1e293b;
color: white;
padding: 10px 20px;
border-radius: 30px;
font-size: 0.9rem;
opacity: 0;
transition: all 0.3s ease;
pointer-events: none;
}
.toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
const menu = document.getElementById("dropdown-menu");
const trigger = document.querySelector(".dropdown-trigger");
function toggleMenu() {
const isOpen = menu.classList.contains("show");
if (isOpen) {
closeMenu();
} else {
openMenu();
}
}
function openMenu() {
menu.classList.add("show");
trigger.classList.add("active");
}
function closeMenu() {
menu.classList.remove("show");
trigger.classList.remove("active");
}
// Dışarı Tıklayınca Kapat
document.addEventListener("click", function (e) {
if (!trigger.contains(e.target) && !menu.contains(e.target)) {
closeMenu();
}
});
// --- HTMX EVENTLERİ (UI Yönetimi) ---
// 1. İstek Başlarken: Loading Göster
document.addEventListener("htmx:beforeRequest", function (e) {
if (e.detail.requestConfig.path === "/api/menu-icerik") {
trigger.classList.add("loading");
}
});
// 2. İstek Bitince: Loading Kaldır
document.addEventListener("htmx:afterRequest", function (e) {
if (e.detail.requestConfig.path === "/api/menu-icerik") {
trigger.classList.remove("loading");
if (e.detail.xhr.status !== 200) {
document.getElementById("menu-content").innerHTML =
'<div style="padding:15px; color:red; text-align:center;">Menü yüklenemedi!</div>';
}
}
});
// Toast Mesajı Göster (Opsiyonel)
function showToast(msg) {
const toast = document.getElementById("toast");
toast.innerText = msg;
toast.classList.add("show");
setTimeout(() => toast.classList.remove("show"), 3000);
}