Özel Düğme İşlemleri

Web özellikleri: Başlatıcı komutları

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ı

  1. Hedef öğeyi tanımlama: İşleme yanıt verecek öğeyi tanımlayın. Benzersiz bir id yoksa ekleyin.
  2. Çağırıcı düğmesini yapılandırın: Hedefin id değerini belirtmek için commandfor özelliğini, özel komut adını (-- ön ekiyle) belirtmek için command özelliğini kullanın.
  3. Komut etkinliğini işleme: Doğrudan hedef öğeye bir command etkinlik işleyici ekleyin. Etkinlik nesnesi, bir command özelliği ve bir target özelliği (commandfor ile tanımlanan öğeyi ifade eder) içerir.
  4. ARIA durumlarını işleme: Özel komutların kendine özgü semantiği yoktur ve aria-pressed veya aria-expanded gibi 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 öğenin id özelliğiyle eşleşmelidir.
  • Kabarcık yok: command etkinliğ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.target yerine event.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 olarak aria-expanded="false" değerine, eşlik eden içerik gösterildiğinde ise aria-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ığında aria-pressed="false", etkin olduğunda ise aria-pressed="true" olarak ayarlayın.
  • Düğme; menu, listbox, tree, grid veya dialog rolüne sahip bir öğeyi açıyorsa denetimin, açtığı öğenin rolüne ayarlanmış bir değere sahip aria-haspopup özelliği olmalıdır.
  • Düğme etkinleştirildiğinde devre dışı kalıyorsa odak kaybı olmaması için disabled yerine aria-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);
  }
 });