Invoker Commands API, düğmelerin HTML özelliklerini kullanarak hedef öğeler üzerinde bildirimli olarak işlem tetiklemesine olanak tanır.
Bu yaklaşım, manuel etkinlik işleyicilere olan ihtiyacı azaltır ve kullanıcı arayüzünü uygulama ayrıntılarından ayırır.
Uygulamaya özel işlemler için kendi komut adlarınızı tanımlayabilirsiniz. Özel komutlar, gelecekteki yerleşik tarayıcı komutlarıyla çakışmaları önlemek için çift tire (--) ile başlamalıdır.
Uygulama adımları
- Hedef öğeyi tanımlama: İşleme yanıt verecek öğeyi tanımlayın. Benzersiz bir
idyoksa ekleyin. - Çağırıcı düğmesini yapılandırın: Hedefin
iddeğerini belirtmek içincommandforözelliğini, özel komut adını (--ön ekiyle) belirtmek içincommandözelliğini kullanın. - Komut etkinliğini işleme: Doğrudan hedef öğeye bir
commandetkinlik işleyici ekleyin. Etkinlik nesnesi, bircommandözelliği ve birtargetözelliği (commandforile tanımlanan öğeyi ifade eder) içerir. - ARIA durumlarını işleme: Özel komutların kendine özgü semantiği yoktur ve
aria-pressedveyaaria-expandedgibi durumları işlemeniz gerekir.
Örnek: Özel Animasyon Kontrolleri
<!-- The target element that will respond to custom commands -->
<div id="action-target" class="target">
Action Target
</div>
<!-- Buttons declaratively linked to the target element -->
<!-- Each button sends a unique custom command starting with '--' -->
<button commandfor="action-target" command="--spin">
Spin
</button>
<button commandfor="action-target" command="--grow">
Grow
</button>
<button commandfor="action-target" command="--reset">
Reset All
</button>
<script>
// 1. **Optional:** Define a registry of requested actions for cleaner logic
const commandRegistry = {
'--spin': (target, source) => {
const isSpun = target.classList.toggle('is-spun');
// Set ARIA states, as custom commands have no inherent semantics.
source?.setAttribute('aria-pressed', isSpun);
},
'--grow': (target, source) => {
const isGrown = target.classList.toggle('is-grown');
source?.setAttribute('aria-pressed', isGrown);
},
'--reset': (target) => {
target.classList.remove('is-spun', 'is-grown');
// Reset all associated buttons' ARIA states
document.querySelectorAll(`button[commandfor="${target.id}"]`).forEach(btn => {
btn.setAttribute('aria-pressed', 'false');
});
},
};
// 2. **Mandatory:** Listen for the 'command' event directly on the target element
// (This is necessary because the native 'command' event does not bubble)
document.getElementById('action-target').addEventListener('command', (event) => {
const command = event.command;
const target = event.target;
const source = event.source; // event.source refers to the triggering button
const action = commandRegistry[command];
if (action) {
action(target, source);
}
});
</script>
Temel kısıtlamalar
- Özel komutlara önek ekleme: ZORUNLU: Tüm özel komut adları
--ile başlamalıdır (ör.command="--my-action"). - Hedefleme:
commandforözelliği, aynı belge ağacındaki bir öğeninidözelliğiyle eşleşmelidir. - Kabarcık yok:
commandetkinliği kabarcık oluşturmaz. Birden fazla olası hedef varsa etkinlik işleyiciye{ capture: true }ekleyin ve bir üst öğeyi dinleyin. - Gölge kökler: Hedef bir gölge kökte olabilirse
event.targetyerineevent.composedPath()[0]kullanın. - Erişilebilirlik: Özel komutların kendine özgü semantiği yoktur ve tüm durumları açıkça uygulamanız gerekir.
Erişilebilirlikle ilgili dikkat edilmesi gereken noktalar
- Düğme ek içerik gösteriyorsa
aria-expandedözelliğini kullanın. Bu özellik varsayılan olarakaria-expanded="false"değerine, eşlik eden içerik gösterildiğinde isearia-expanded="true"değerine ayarlanır. - Düğme, etkileşimi kolaylaştırıyorsa (ör. oynatıp duraklatabilen açma/kapatma düğmeleri, sesi kapatıp açabilen ses düğmeleri)
aria-pressedözelliğini kullanın, etkin olmadığındaaria-pressed="false", etkin olduğunda isearia-pressed="true"olarak ayarlayın. - Düğme;
menu,listbox,tree,gridveyadialogrolüne sahip bir öğeyi açıyorsa denetimin, açtığı öğenin rolüne ayarlanmış bir değere sahiparia-haspopupözelliği olmalıdır. - Düğme etkinleştirildiğinde devre dışı kalıyorsa odak kaybı olmaması için
disabledyerinearia-disabled="true"kullanın.
Yedek stratejiler
Invoker Commands API desteklenmiyorsa command etkinliği tetiklenmez. Tüm modern tarayıcılarda tam destek için npm install veya CDN aracılığıyla https://github.com/keithamus/invokers-polyfill adresindeki invokers-polyfill'i kullanmanız önerilir.
Bu polyfill, özel işlemleri (-- ile başlayan) tam olarak destekler ve command etkinliğini tam olarak yerel API gibi gönderir.
Dinamik İçe Aktarma (Performans Optimizasyonu)
En iyi performans için polyfill'i yalnızca tarayıcı API'yi yerel olarak desteklemiyorsa yüklemeniz gerekir. Bu sayede, modern tarayıcıları kullanan kullanıcılar için bant genişliğinden tasarruf edilir ve komut dosyası yürütme süresi kısalır.
NOT: Bu polyfill, özel komutlar için ARIA durumlarını (aria-pressed veya aria-expanded gibi) yönetmez. Sitenizin erişilebilir olmasını sağlamak için etkinlik işleyicinizde bu durumları manuel olarak senkronize etmeniz gerekir.
// 1. Conditionally load the polyfill
const hasNativeSupport = 'commandForElement' in HTMLButtonElement.prototype;
if (!hasNativeSupport) {
// Wrap in an async IIFE to avoid top-level await issues in older browsers
(async () => {
try {
await import('https://esm.run/invokers-polyfill');
} catch (err) {
console.error('Error loading fallback:', err);
}
})();
}
// 2. Manually manage ARIA states in your listener
document.getElementById('action-target').addEventListener('command', (event) => {
const command = event.command;
const target = event.target;
const source = event.source; // The button that triggered the command
if (command === '--spin') {
const isSpun = target.classList.toggle('is-spun');
// Polyfill tip: Manually update ARIA to match the new state
source?.setAttribute('aria-pressed', isSpun);
}
});
Manuel yedek (geleneksel model)
Polyfill kullanmak istemiyorsanız etkinlikleri göndermek için etkinlik yetkilendirme ve işlemleri gerçekleştirmek için komut kayıt defteri kombinasyonunu kullanabilirsiniz. Bu, geleneksel JavaScript geliştirmede yaygın olarak kullanılan ve son derece verimli ve ölçeklenebilir bir mimari modeldir.
// 1. **Optional:** Define a registry of requested actions for cleaner logic
const commandRegistry = {
'--spin': (target) => target.classList.toggle('is-spun'),
'--grow': (target) => target.classList.toggle('is-grown'),
'--reset': (target) => target.classList.remove('is-spun', 'is-grown'),
};
// 2. If CommandEvent doesn't exist, we assume no native support and provide the fallback
if (!globalThis.CommandEvent) {
globalThis.CommandEvent = class CommandEvent extends Event {
constructor(type, { source, command, ...options } = {}) {
super(type, options);
this.source = source;
this.command = command;
}
}
}
// 3. The fallback: Dispatch events manually if native support is missing
document.addEventListener('click', (event) => {
const button = event.composedPath().find((el) => el.matches?.("button[commandfor]"));
if (!button) return;
const target = document.getElementById(button.getAttribute('commandfor'));
const command = button.getAttribute('command');
if (target && command) {
target.dispatchEvent(new CommandEvent('command', {
command,
source: button,
}));
}
});
// 4. **Mandatory:** Register the unified listener directly on the target element
document.getElementById('action-target').addEventListener('command', (event) => {
const command = event.command;
const target = event.target;
const action = commandRegistry[command];
if (action) {
action(target);
}
});