কাস্টম বোতাম অ্যাকশন

ওয়েব ফিচার: ইনভোকার কমান্ড

ইনভোকার কমান্ড API, HTML অ্যাট্রিবিউট ব্যবহার করে টার্গেট এলিমেন্টে অ্যাকশন ট্রিগার করার জন্য বোতামকে অনুমতি দেয়। এই পদ্ধতি ম্যানুয়াল ইভেন্ট লিসনারের প্রয়োজনীয়তা কমায় এবং ইমপ্লিমেন্টেশনের বিবরণ থেকে UI-কে আলাদা করে। কাস্টম, অ্যাপ্লিকেশন-নির্দিষ্ট অ্যাকশনের জন্য, আপনি নিজের কমান্ডের নাম নির্ধারণ করতে পারেন। ভবিষ্যতে বিল্ট-ইন ব্রাউজার কমান্ডের সাথে যাতে কোনও সংঘর্ষ না হয়, তার জন্য কাস্টম কমান্ডের আগে ডবল ড্যাশ (--) থাকতে হবে।

প্রয়োগ করার বিভিন্ন ধাপ

  1. টার্গেট এলিমেন্ট নির্ধারণ করুন: অ্যাকশনে সাড়া দেবে এমন এলিমেন্ট শনাক্ত করুন। এটির কোনও অনন্য id না থাকলে, একটি যোগ করুন।
  2. ইনভোকার বোতাম কনফিগার করুন: টার্গেটের id-এ পয়েন্ট করতে commandfor অ্যাট্রিবিউট এবং কাস্টম কমান্ডের নাম (-- প্রিফিক্স সহ) নির্দিষ্ট করতে command অ্যাট্রিবিউট ব্যবহার করুন।
  3. কমান্ড ইভেন্ট ম্যানেজ করা: টার্গেট এলিমেন্টে সরাসরি একটি command ইভেন্ট লিসনার অ্যাটাচ করুন। ইভেন্ট অবজেক্টে একটি command প্রপার্টি এবং একটি target প্রপার্টি (commandfor দ্বারা শনাক্ত করা এলিমেন্টকে রেফার করে) থাকে।
  4. aria স্টেট ম্যানেজ করা: কাস্টম কমান্ডের কোনও অন্তর্নিহিত শব্দার্থ নেই এবং আপনাকে aria-pressed বা aria-expanded-এর মতো স্টেট ম্যানেজ করতে হবে।

উদাহরণ: কাস্টম অ্যানিমেশন কন্ট্রোল

<!-- 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>

মূল বাধ্যবাধকতা

  • কাস্টম কমান্ডের প্রিফিক্স: বাধ্যতামূলক: সব কাস্টম কমান্ডের নাম অবশ্যই -- (যেমন, command="--my-action") দিয়ে শুরু হতে হবে।
  • টার্গেট করা: commandfor অ্যাট্রিবিউটটি একই ডকুমেন্ট ট্রির একটি এলিমেন্টের id-এর সাথে মিলতে হবে।
  • কোনও বাবলিং নেই: command ইভেন্ট বাবল করে না। একাধিক সম্ভাব্য টার্গেট থাকলে, ইভেন্ট হ্যান্ডলারে { capture: true } যোগ করুন এবং পূর্বসূরীর উপর শুনুন।
  • শ্যাডো রুট: টার্গেট শ্যাডো রুটে থাকলে, event.target-এর পরিবর্তে event.composedPath()[0] ব্যবহার করুন।
  • অ্যাক্সেসিবিলিটি: কাস্টম কমান্ডের কোনও অন্তর্নিহিত শব্দার্থ নেই এবং আপনাকে অবশ্যই স্পষ্টভাবে যেকোনও স্ট্যাটাস প্রয়োগ করতে হবে।

অ্যাক্সেসিবিলিটি সংক্রান্ত বিষয়

  • বোতামটি অতিরিক্ত কন্টেন্ট দেখালে, aria-expanded অ্যাট্রিবিউট ব্যবহার করুন, ডিফল্ট হিসেবে aria-expanded="false"-এ সেট করুন এবং সংশ্লিষ্ট কন্টেন্ট দেখানো হলে aria-expanded="true"-এ সেট করুন
  • বোতামটি নিজেই কোনও ইন্টার‍্যাকশন সহজ করলে (উদাহরণস্বরূপ, টগল বোতাম যা প্লে ও পজ করতে পারে, ভলিউম বোতাম যা মিউট ও আনমিউট করতে পারে), aria-pressed অ্যাট্রিবিউট ব্যবহার করুন, যা ইনঅ্যাক্টিভ থাকলে aria-pressed="false" এবং অ্যাক্টিভ থাকলে aria-pressed="true" হিসেবে সেট করা থাকে
  • বোতামটি যদি menu, listbox, tree, grid অথবা dialog-এর মধ্যে কোনও একটি ভূমিকা সহ এলিমেন্ট খোলে, তাহলে কন্ট্রোলে অবশ্যই একটি aria-haspopup অ্যাট্রিবিউট থাকতে হবে, যার মান এটি যে এলিমেন্ট খোলে তার ভূমিকার সাথে সেট করা থাকে
  • অ্যাক্টিভেশনের সময় বোতামটি বন্ধ হয়ে গেলে, disabled-এর পরিবর্তে aria-disabled="true" ব্যবহার করুন যাতে ফোকাস নষ্ট না হয়

ফলব্যাক স্ট্র্যাটেজি

Invoker Commands API কাজ না করলে, command ইভেন্ট ট্রিগার হবে না। সব আধুনিক ব্রাউজার জুড়ে সম্পূর্ণ সহায়তা পেতে, npm install বা CDN-এর মাধ্যমে https://github.com/keithamus/invokers-polyfill থেকে invokers-polyfill ব্যবহার করার সাজেশন দেওয়া হয়।

এই পলিফিল কাস্টম অ্যাকশন (-- দিয়ে শুরু) সম্পূর্ণভাবে সাপোর্ট করে এবং নেটিভ API-এর মতো ঠিক একইভাবে command ইভেন্ট ডিসপ্যাচ করে।

ডায়নামিক ইমপোর্ট (পারফর্ম্যান্স অপ্টিমাইজেশন)

সেরা পারফর্ম্যান্সের জন্য, ব্রাউজার নেটিভভাবে API সাপোর্ট না করলে, আপনার শুধুমাত্র পলিফিল লোড করা উচিত। এটি ব্যান্ডউইথ বাঁচায় এবং আধুনিক ব্রাউজার ব্যবহারকারীদের জন্য স্ক্রিপ্ট এক্সিকিউশনের সময় কমায়।

মনে রাখবেন: এই পলিফিল কাস্টম কমান্ডের জন্য ARIA স্ট্যাটাস (যেমন aria-pressed বা aria-expanded) ম্যানেজ করে না। আপনার সাইট যাতে অ্যাক্সেস করা যায় তা নিশ্চিত করতে, আপনার ইভেন্ট লিসেনারে এইসব স্ট্যাটাস ম্যানুয়ালি সিঙ্ক করতে হবে।

// 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);
  }
});

ম্যানুয়াল ফলব্যাক (ঐতিহ্যগত প্যাটার্ন)

আপনি পলিফিল ব্যবহার করতে না চাইলে, ইভেন্ট ডিসপ্যাচ করার জন্য ইভেন্ট ডেলিগেশন এবং অ্যাকশন ম্যানেজ করার জন্য কমান্ড রেজিস্ট্রি-এর কম্বিনেশন ব্যবহার করতে পারেন। এটি হল প্রথাগত জাভাস্ক্রিপ্ট ডেভেলপমেন্টের একটি সাধারণ আর্কিটেকচারাল প্যাটার্ন যা অত্যন্ত দক্ষ এবং স্কেলেবল।

// 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);
  }
 });