कस्टम एलिमेंट v1 - फिर से इस्तेमाल किए जा सकने वाले वेब कॉम्पोनेंट

कस्टम एलिमेंट की मदद से, वेब डेवलपर नए एचटीएमएल टैग तय कर सकते हैं, मौजूदा टैग को बढ़ा सकते हैं, और फिर से इस्तेमाल किए जा सकने वाले वेब कॉम्पोनेंट बना सकते हैं.

एरिक बिडेलमैन

कस्टम एलिमेंट की मदद से वेब डेवलपर नए एचटीएमएल टैग बना सकते हैं, मौजूदा एचटीएमएल टैग को बेहतर बना सकते हैं या दूसरे डेवलपर के लिखे गए कॉम्पोनेंट को बढ़ा सकते हैं. एपीआई, वेब कॉम्पोनेंट का बुनियादी हिस्सा है. इसमें सिर्फ़ JS/HTML/CSS का इस्तेमाल करके, फिर से इस्तेमाल किए जा सकने वाले कॉम्पोनेंट बनाए जा सकते हैं. यह तरीका वेब स्टैंडर्ड पर आधारित है. इसकी वजह से, कम कोड और मॉड्यूलर कोड की ज़रूरत पड़ती है. साथ ही, ऐप्लिकेशन में उन्हें बार-बार इस्तेमाल करने की ज़रूरत होती है.

शुरुआती जानकारी

ब्राउज़र वेब ऐप्लिकेशन बनाने के लिए एक बेहतरीन टूल उपलब्ध कराता है. इसे एचटीएमएल कहा जाता है. शायद आपने इसके बारे में सुना होगा! यह जानकारी देने वाला, पोर्टेबल, अच्छी तरह से काम करने वाला, और इस्तेमाल करने में आसान है. HTML भले ही शानदार हो, लेकिन इसकी शब्दावली और विस्तार की क्षमता सीमित है. एचटीएमएल लिविंग स्टैंडर्ड में, आपके मार्कअप से JS व्यवहार को अपने-आप जोड़ने का कोई तरीका अब तक मौजूद नहीं है....

कस्टम एलिमेंट से एचटीएमएल को आधुनिक बनाया जा सकता है, छूटी हुई चीज़ों को भरा जा सकता है, और व्यवहार के साथ स्ट्रक्चर को बंडल किया जा सकता है. अगर एचटीएमएल किसी समस्या का समाधान नहीं कर पाता, तो हम ऐसा कस्टम एलिमेंट बना सकते हैं. कस्टम एलिमेंट, एचटीएमएल के फ़ायदे जारी रखते हुए, ब्राउज़र को नई तरकीबें सिखाते हैं.

नए एलिमेंट की जानकारी देना

नया एचटीएमएल एलिमेंट तय करने के लिए हमें JavaScript की ताकत की ज़रूरत होती है!

customElements ग्लोबल का इस्तेमाल कस्टम एलिमेंट तय करने और ब्राउज़र को नए टैग के बारे में जानकारी देने के लिए किया जाता है. customElements.define() को उस टैग नाम के साथ कॉल करें जिसे आपको बनाना है. साथ ही, एक JavaScript class को कॉल करें जो बेस HTMLElement का विस्तार करता हो.

उदाहरण - मोबाइल पैनल पैनल तय करना, <app-drawer>:

class AppDrawer extends HTMLElement {...}
window.customElements.define('app-drawer', AppDrawer);

// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer', class extends HTMLElement {...});

इस्तेमाल के उदाहरण:

<app-drawer></app-drawer>

यह ध्यान रखना ज़रूरी है कि कस्टम एलिमेंट का इस्तेमाल करना, <div> या किसी दूसरे एलिमेंट को इस्तेमाल करने से अलग नहीं है. इंस्टेंस, पेज पर बताए जा सकते हैं, उन्हें डाइनैमिक तौर पर JavaScript में बनाया जा सकता है, इवेंट लिसनर को अटैच किया जा सकता है वगैरह. ज़्यादा उदाहरणों के लिए, पढ़ना जारी रखें.

किसी एलिमेंट के JavaScript API के बारे में जानकारी

कस्टम एलिमेंट के फ़ंक्शन को ES2015 का इस्तेमाल करके तय किया जाता है class जो HTMLElement के दायरे में आता है. HTMLElement को बढ़ाने से यह पक्का होता है कि कस्टम एलिमेंट, पूरे DOM API को देता है. इसका मतलब है कि क्लास में जोड़ी जाने वाली कोई भी प्रॉपर्टी/तरीका, एलिमेंट के DOM इंटरफ़ेस का हिस्सा बन जाता है. हर टैग के लिए public JavaScript API बनाने के लिए, क्लास का इस्तेमाल करें.

उदाहरण - <app-drawer> का DOM इंटरफ़ेस तय करना:

class AppDrawer extends HTMLElement {

  // A getter/setter for an open property.
  get open() {
    return this.hasAttribute('open');
  }

  set open(val) {
    // Reflect the value of the open property as an HTML attribute.
    if (val) {
      this.setAttribute('open', '');
    } else {
      this.removeAttribute('open');
    }
    this.toggleDrawer();
  }

  // A getter/setter for a disabled property.
  get disabled() {
    return this.hasAttribute('disabled');
  }

  set disabled(val) {
    // Reflect the value of the disabled property as an HTML attribute.
    if (val) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }

  // Can define constructor arguments if you wish.
  constructor() {
    // If you define a constructor, always call super() first!
    // This is specific to CE and required by the spec.
    super();

    // Setup a click listener on <app-drawer> itself.
    this.addEventListener('click', e => {
      // Don't toggle the drawer if it's disabled.
      if (this.disabled) {
        return;
      }
      this.toggleDrawer();
    });
  }

  toggleDrawer() {
    // ...
  }
}

customElements.define('app-drawer', AppDrawer);

इस उदाहरण में, हम एक ऐसा पैनल बना रहे हैं जिसमें एक open प्रॉपर्टी, disabled प्रॉपर्टी, और एक toggleDrawer() तरीका है. यह प्रॉपर्टी को एचटीएमएल एट्रिब्यूट के रूप में भी दिखाता है.

कस्टम एलिमेंट की एक खास सुविधा यह है कि क्लास डेफ़िनिशन में this का मतलब डीओएम एलिमेंट से होता है, जैसे कि क्लास का इंस्टेंस. हमारे उदाहरण में, this का मतलब <app-drawer> है. इस (😉) की मदद से, एलिमेंट ऐसे click लिसनर को अपने साथ जोड़ सकता है! और आप सिर्फ़ इवेंट श्रोताओं तक ही सीमित नहीं हैं. एलिमेंट कोड में पूरा DOM एपीआई उपलब्ध है. एलिमेंट की प्रॉपर्टी ऐक्सेस करने, इसके चाइल्ड (this.children), क्वेरी नोड (this.querySelectorAll('.items')) वगैरह की जांच करने के लिए, this का इस्तेमाल करें.

कस्टम एलिमेंट बनाने के लिए नियम

  1. कस्टम एलिमेंट के नाम में डैश (-) होना चाहिए. इसलिए, <x-tags>, <my-element>, और <my-awesome-app> सभी मान्य नाम हैं, जबकि <tabs> और <foo_bar> नहीं हैं. ऐसा इसलिए है, ताकि एचटीएमएल पार्सर कस्टम एलिमेंट और सामान्य एलिमेंट की पहचान कर सके. इससे यह भी पक्का होता है कि एचटीएमएल में नए टैग जोड़ने पर, फ़ॉरवर्ड करने की प्रोसेस पूरी हो जाएगी.
  2. एक ही टैग को एक से ज़्यादा बार रजिस्टर नहीं किया जा सकता है. ऐसा करने की कोशिश करने पर, DOMException दिखेगा. ब्राउज़र को एक बार नए टैग के बारे में बताने के बाद, बस ऐसा ही होता है. कोई टेक बैक नहीं.
  3. कस्टम एलिमेंट अपने-आप बंद होने वाले एलिमेंट नहीं हो सकते, क्योंकि एचटीएमएल में कुछ ही एलिमेंट अपने-आप बंद हो सकते हैं. हमेशा एक क्लोज़िंग टैग लिखें (<app-drawer></app-drawer>).

कस्टम एलिमेंट वाली प्रतिक्रियाएं

कस्टम एलिमेंट, खास लाइफ़साइकल हुक के लिए यह तय कर सकता है कि कोई खास लाइफ़साइकल हुक मौजूद है या नहीं. इस हुक की मदद से, इस दौरान कोड को चलाया जा सकता है. इन्हें कस्टम एलिमेंट प्रतिक्रियाएं कहा जाता है.

नाम कॉल कब किया गया
constructor एलिमेंट का एक इंस्टेंस बनाया या अपग्रेड किया गया है. यह स्थिति शुरू करने, इवेंट लिसनर को सेट करने या शैडो डॉम बनाने के लिए इस्तेमाल की जाती है. constructor में क्या-क्या किया जा सकता है, इससे जुड़ी पाबंदियों के बारे में जानने के लिए, स्पेसिफ़िकेशन देखें.
connectedCallback हर बार एलिमेंट को डीओएम में डालने पर इसे कॉल किया जाता है. यह सेटअप कोड चलाने के लिए काम की होती है. जैसे, रिसॉर्स फ़ेच करना या रेंडरिंग. आम तौर पर, आपको इस समय तक काम को देर से करना चाहिए.
disconnectedCallback डीओएम से एलिमेंट हटाए जाने पर हर बार कॉल किया जाता है. क्लीन अप कोड चलाने के लिए इसका इस्तेमाल किया जाता है.
attributeChangedCallback(attrName, oldVal, newVal) यह तब कॉल किया जाता है, जब निगरानी में रखा गया एट्रिब्यूट जोड़ा गया हो, हटाया गया हो, अपडेट किया गया हो या बदला गया हो. इन्हें शुरुआती वैल्यू भी कहा जाता है. ऐसा तब होता है, जब पार्सर ने कोई एलिमेंट बनाया हो या अपग्रेड किया हो. ध्यान दें: यह कॉलबैक सिर्फ़ observedAttributes प्रॉपर्टी में दिए गए एट्रिब्यूट को ही मिलेगा.
adoptedCallback कस्टम एलिमेंट को नए document में ले जाया गया है (उदाहरण के लिए, document.adoptNode(el) नाम का कोई व्यक्ति).

रिऐक्शन कॉलबैक सिंक्रोनस होते हैं. अगर कोई व्यक्ति आपके एलिमेंट पर el.setAttribute() कॉल करता है, तो ब्राउज़र तुरंत attributeChangedCallback() को कॉल करेगा. इसी तरह, DOM से आपका एलिमेंट हटाए जाने के तुरंत बाद आपको disconnectedCallback() मिलेगा (उदाहरण के लिए, उपयोगकर्ता el.remove() कॉल करता है).

उदाहरण: <app-drawer> में कस्टम एलिमेंट वाली प्रतिक्रियाएं जोड़ना:

class AppDrawer extends HTMLElement {
  constructor() {
    super(); // always call super() first in the constructor.
    // ...
  }

  connectedCallback() {
    // ...
  }

  disconnectedCallback() {
    // ...
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    // ...
  }
}

प्रतिक्रियाओं के बारे में बताएं, अगर/जब उनका मतलब समझ में आता है. अगर आपका एलिमेंट काफ़ी जटिल है और connectedCallback() में IndexedDB से कनेक्शन खुलता है, तो disconnectedCallback() में ज़रूरी क्लीनअप का काम करें. लेकिन ध्यान रखें! हर हाल में डीओएम से अपने एलिमेंट को हटाए जाने पर भरोसा नहीं किया जा सकता. उदाहरण के लिए, अगर उपयोगकर्ता टैब बंद कर देता है, तो disconnectedCallback() को कभी कॉल नहीं किया जाएगा.

प्रॉपर्टी और एट्रिब्यूट

एट्रिब्यूट के हिसाब से प्रॉपर्टी की जानकारी देना

आम तौर पर, एचटीएमएल प्रॉपर्टी के लिए वैल्यू को, डीओएम में एचटीएमएल एट्रिब्यूट के तौर पर दिखाना आम बात होती है. उदाहरण के लिए, जब JS में hidden या id की वैल्यू बदली जाती हैं:

div.id = 'my-id';
div.hidden = true;

वैल्यू, लाइव DOM पर एट्रिब्यूट के तौर पर लागू होती हैं:

<div id="my-id" hidden>

इसे "एट्रिब्यूट में प्रॉपर्टी दिखाना" कहा जाता है. एचटीएमएल की करीब-करीब हर प्रॉपर्टी ऐसा करती है. ऐसा क्यों है? एट्रिब्यूट की मदद से, किसी एलिमेंट को डिक्लेरेटिव टोन में भी कॉन्फ़िगर किया जा सकता है. साथ ही, सुलभता और सीएसएस जैसे कुछ एपीआई, काम करने के लिए एट्रिब्यूट पर निर्भर होते हैं.

प्रॉपर्टी के बारे में जानकारी उन सभी जगहों पर इस्तेमाल की जा सकती है जहां आपको एलिमेंट के डीओएम प्रज़ेंटेशन को उसकी JavaScript स्थिति के साथ सिंक रखना हो. प्रॉपर्टी को दिखाने की एक वजह यह हो सकती है कि JS की स्थिति बदलने पर उपयोगकर्ता की तय की गई स्टाइल लागू होती है.

हमारे <app-drawer> को फिर से बुलाएँ. इस कॉम्पोनेंट का इस्तेमाल करने वाला व्यक्ति, इसे धुंधला करके/या बंद किए जाने पर उपयोगकर्ता के इंटरैक्शन को रोक सकता है:

app-drawer[disabled] {
  opacity: 0.5;
  pointer-events: none;
}

जब JS में disabled प्रॉपर्टी को बदला जाता है, तो हम चाहते हैं कि उस एट्रिब्यूट को डीओएम में जोड़ा जाए, ताकि उपयोगकर्ता के सिलेक्टर को मैच किया जा सके. एलिमेंट में, उसी नाम की किसी एट्रिब्यूट की वैल्यू को दिखाकर वह व्यवहार मिल सकता है:

get disabled() {
  return this.hasAttribute('disabled');
}

set disabled(val) {
  // Reflect the value of `disabled` as an attribute.
  if (val) {
    this.setAttribute('disabled', '');
  } else {
    this.removeAttribute('disabled');
  }
  this.toggleDrawer();
}

एट्रिब्यूट में हुए बदलावों पर नज़र रखना

एचटीएमएल एट्रिब्यूट की मदद से, उपयोगकर्ता शुरुआती स्थिति के बारे में आसानी से बता सकते हैं:

<app-drawer open disabled></app-drawer>

attributeChangedCallback तय करके, एलिमेंट, एट्रिब्यूट में हुए बदलावों पर प्रतिक्रिया दे सकते हैं. ब्राउज़र, observedAttributes कलेक्शन में मौजूद एट्रिब्यूट में किए गए हर बदलाव के लिए, इस तरीके को कॉल करेगा.

class AppDrawer extends HTMLElement {
  // ...

  static get observedAttributes() {
    return ['disabled', 'open'];
  }

  get disabled() {
    return this.hasAttribute('disabled');
  }

  set disabled(val) {
    if (val) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }

  // Only called for the disabled and open attributes due to observedAttributes
  attributeChangedCallback(name, oldValue, newValue) {
    // When the drawer is disabled, update keyboard/screen reader behavior.
    if (this.disabled) {
      this.setAttribute('tabindex', '-1');
      this.setAttribute('aria-disabled', 'true');
    } else {
      this.setAttribute('tabindex', '0');
      this.setAttribute('aria-disabled', 'false');
    }
    // TODO: also react to the open attribute changing.
  }
}

इस उदाहरण में, disabled एट्रिब्यूट बदलने पर, हम <app-drawer> पर अतिरिक्त एट्रिब्यूट सेट कर रहे हैं. हालांकि, हम यह काम यहां नहीं कर रहे हैं, लेकिन आपके पास किसी JS प्रॉपर्टी को उसके एट्रिब्यूट के साथ सिंक करने के लिए, attributeChangedCallback का इस्तेमाल करने का विकल्प भी है.

एलिमेंट अपग्रेड करना

धीरे-धीरे बेहतर बनाया गया एचटीएमएल

हम पहले ही जान चुके हैं कि कस्टम एलिमेंट, customElements.define() को कॉल करके तय किए जाते हैं. हालांकि, इसका मतलब यह नहीं है कि आपको एक ही बार में कस्टम एलिमेंट तय और रजिस्टर करना होगा.

कस्टम एलिमेंट का इस्तेमाल, उनकी डेफ़िनिशन के रजिस्टर होने से पहले किया जा सकता है.

प्रोग्रेसिव एन्हैंसमेंट, कस्टम एलिमेंट की एक सुविधा है. दूसरे शब्दों में, पेज पर कई <app-drawer> एलिमेंट शामिल किए जा सकते हैं और customElements.define('app-drawer', ...) को कभी भी शुरू नहीं किया जा सकता. ऐसा इसलिए होता है, क्योंकि ब्राउज़र संभावित कस्टम एलिमेंट के साथ अलग तरह से काम करता है. इसके लिए, अनजान टैग की मदद से ऐसा किया जाता है. define() को कॉल करने और क्लास की परिभाषा वाले किसी मौजूदा एलिमेंट को खत्म करने की प्रोसेस को "एलिमेंट अपग्रेड" कहते हैं.

टैग का नाम कब तय होता है, यह जानने के लिए window.customElements.whenDefined() का इस्तेमाल किया जा सकता है. यह ऐसा प्रॉमिस देता है जो एलिमेंट के परिभाषित होने पर रिज़ॉल्व हो जाता है.

customElements.whenDefined('app-drawer').then(() => {
  console.log('app-drawer defined');
});

उदाहरण - चाइल्ड एलिमेंट के सेट को अपग्रेड होने तक के काम को देरी से पूरा करें

<share-buttons>
  <social-button type="twitter"><a href="...">Twitter</a></social-button>
  <social-button type="fb"><a href="...">Facebook</a></social-button>
  <social-button type="plus"><a href="...">G+</a></social-button>
</share-buttons>
// Fetch all the children of <share-buttons> that are not defined yet.
let undefinedButtons = buttons.querySelectorAll(':not(:defined)');

let promises = [...undefinedButtons].map((socialButton) => {
  return customElements.whenDefined(socialButton.localName);
});

// Wait for all the social-buttons to be upgraded.
Promise.all(promises).then(() => {
  // All social-button children are ready.
});

एलिमेंट के हिसाब से तय किया गया कॉन्टेंट

कस्टम एलिमेंट, एलिमेंट कोड में DOM एपीआई का इस्तेमाल करके अपना कॉन्टेंट मैनेज कर सकते हैं. इसके लिए, प्रतिक्रियाओं का इस्तेमाल करना आसान होता है.

उदाहरण - कुछ डिफ़ॉल्ट एचटीएमएल के साथ कोई एलिमेंट बनाएं:

customElements.define('x-foo-with-markup', class extends HTMLElement {
  connectedCallback() {
    this.innerHTML = "<b>I'm an x-foo-with-markup!</b>";
  }
  // ...
});

इस टैग का एलान करने से ये नतीजे मिलेंगे:

<x-foo-with-markup>
  <b>I'm an x-foo-with-markup!</b>
</x-foo-with-markup>

// TODO: DevSite - कोड सैंपल को हटा दिया गया, क्योंकि उसमें इनलाइन इवेंट हैंडलर का इस्तेमाल किया गया था

शैडो DOM का इस्तेमाल करने वाला एलिमेंट बनाना

शैडो DOM किसी एलिमेंट को DOM के एक हिस्से का मालिक बनने, रेंडर करने, और उसे स्टाइल करने का तरीका देता है. यह हिस्सा बाकी पेज से अलग होता है. वाह, आप एक ही टैग के अंदर पूरा ऐप छिपा सकते हैं:

<!-- chat-app's implementation details are hidden away in Shadow DOM. -->
<chat-app></chat-app>

किसी कस्टम एलिमेंट में Shadow DOM का इस्तेमाल करने के लिए, अपने constructor में this.attachShadow को कॉल करें:

let tmpl = document.createElement('template');
tmpl.innerHTML = `
  <style>:host { ... }</style> <!-- look ma, scoped styles -->
  <b>I'm in shadow dom!</b>
  <slot></slot>
`;

customElements.define('x-foo-shadowdom', class extends HTMLElement {
  constructor() {
    super(); // always call super() first in the constructor.

    // Attach a shadow root to the element.
    let shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.appendChild(tmpl.content.cloneNode(true));
  }
  // ...
});

इस्तेमाल के उदाहरण:

<x-foo-shadowdom>
  <p><b>User's</b> custom text</p>
</x-foo-shadowdom>

<!-- renders as -->
<x-foo-shadowdom>
  #shadow-root
  <b>I'm in shadow dom!</b>
  <slot></slot> <!-- slotted content appears here -->
</x-foo-shadowdom>

उपयोगकर्ता का पसंद के मुताबिक बनाया गया टेक्स्ट

// TODO: DevSite - कोड सैंपल को हटा दिया गया, क्योंकि उसमें इनलाइन इवेंट हैंडलर का इस्तेमाल किया गया था

<template> से एलिमेंट बनाना

ऐसे लोगों के लिए <template> एलिमेंट आपको डीओएम के फ़्रैगमेंट के बारे में बताने की अनुमति देता है जिन्हें पार्स किया गया है, पेज लोड होने पर इनऐक्टिव है, और बाद में रनटाइम के दौरान चालू किया जा सकता है. यह वेब कॉम्पोनेंट फ़ैमिली में एक और एपीआई प्रिमिटिव है. कस्टम एलिमेंट के स्ट्रक्चर का एलान करने के लिए, टेंप्लेट सबसे सही प्लेसहोल्डर होता है.

उदाहरण: <template> से बनाए गए शैडो DOM कॉन्टेंट के साथ किसी एलिमेंट को रजिस्टर करना:

<template id="x-foo-from-template">
  <style>
    p { color: green; }
  </style>
  <p>I'm in Shadow DOM. My markup was stamped from a &lt;template&gt;.</p>
</template>

<script>
  let tmpl = document.querySelector('#x-foo-from-template');
  // If your code is inside of an HTML Import you'll need to change the above line to:
  // let tmpl = document.currentScript.ownerDocument.querySelector('#x-foo-from-template');

  customElements.define('x-foo-from-template', class extends HTMLElement {
    constructor() {
      super(); // always call super() first in the constructor.
      let shadowRoot = this.attachShadow({mode: 'open'});
      shadowRoot.appendChild(tmpl.content.cloneNode(true));
    }
    // ...
  });
</script>

कोड की ये कुछ लाइनें, चुटकी का काम करती हैं. आइए, अहम चीज़ों को समझते हैं:

  1. हम एचटीएमएल में एक नया एलिमेंट तय कर रहे हैं: <x-foo-from-template>
  2. एलिमेंट का शैडो DOM <template> से बनाया गया है
  3. एलिमेंट का DOM, एलिमेंट के लिए लोकल है. ऐसा, शैडो DOM की वजह से हुआ है
  4. शैडो डीओएम की मदद से, एलिमेंट का इंटरनल सीएसएस, एलिमेंट तक सीमित किया गया है

मैं शैडो DOM में हूं. मेरे मार्कअप पर <template> से स्टैंप लगाया गया था.

// TODO: DevSite - कोड सैंपल को हटा दिया गया, क्योंकि उसमें इनलाइन इवेंट हैंडलर का इस्तेमाल किया गया था

कस्टम एलिमेंट का लुक तय करना

भले ही आपका एलिमेंट Shadow DOM का इस्तेमाल करके अपनी स्टाइल तय करता हो, लेकिन उपयोगकर्ता अपने पेज से आपके कस्टम एलिमेंट को स्टाइल दे सकते हैं. इन्हें "उपयोगकर्ता के तय किए गए स्टाइल" कहा जाता है.

<!-- user-defined styling -->
<style>
  app-drawer {
    display: flex;
  }
  panel-item {
    transition: opacity 400ms ease-in-out;
    opacity: 0.3;
    flex: 1;
    text-align: center;
    border-radius: 50%;
  }
  panel-item:hover {
    opacity: 1.0;
    background: rgb(255, 0, 255);
    color: white;
  }
  app-panel > panel-item {
    padding: 5px;
    list-style: none;
    margin: 0 7px;
  }
</style>

<app-drawer>
  <panel-item>Do</panel-item>
  <panel-item>Re</panel-item>
  <panel-item>Mi</panel-item>
</app-drawer>

आपके मन में यह सवाल आ सकता है कि अगर एलिमेंट में Shadow DOM में स्टाइल तय की गई है, तो CSS की विशेषता कैसे काम करती है. अगर बात सटीक है, तो उपयोगकर्ता स्टाइल की जीत होती है. वे एलिमेंट से तय किए गए स्टाइल को हमेशा बदल देंगे. Shadow DOM का इस्तेमाल करने वाला एलिमेंट बनाना सेक्शन देखें.

रजिस्टर नहीं किए गए एलिमेंट को पहले से स्टाइल करना

किसी एलिमेंट को अपग्रेड करने से पहले, उसे सीएसएस में टारगेट करने के लिए, :defined स्यूडो-क्लास का इस्तेमाल किया जा सकता है. यह किसी कॉम्पोनेंट को पहले से स्टाइल करने के लिए फ़ायदेमंद है. उदाहरण के लिए, हो सकता है कि आप लेआउट या अन्य विज़ुअल FOUC को रोकने के लिए, तय न किए गए कॉम्पोनेंट को छिपाएं और तय होने पर उन्हें फ़ेड इन करें.

उदाहरण - तय करने से पहले <app-drawer> छिपाएं:

app-drawer:not(:defined) {
  /* Pre-style, give layout, replicate app-drawer's eventual styles, etc. */
  display: inline-block;
  height: 100vh;
  opacity: 0;
  transition: opacity 0.3s ease-in-out;
}

<app-drawer> तय होने के बाद, सिलेक्टर (app-drawer:not(:defined)) मेल नहीं खाता.

एलिमेंट को बड़ा करना

Custom Elements API नए एचटीएमएल एलिमेंट बनाने का काम करता है, लेकिन यह दूसरे कस्टम एलिमेंट या ब्राउज़र में पहले से मौजूद एचटीएमएल को बढ़ाने में भी मददगार है.

कस्टम एलिमेंट को बढ़ाना

किसी अन्य कस्टम एलिमेंट को बढ़ाने के लिए, उसकी क्लास की परिभाषा को बढ़ाया जाता है.

उदाहरण - <app-drawer> का विस्तार करने वाला <fancy-app-drawer> बनाएं:

class FancyDrawer extends AppDrawer {
  constructor() {
    super(); // always call super() first in the constructor. This also calls the extended class' constructor.
    // ...
  }

  toggleDrawer() {
    // Possibly different toggle implementation?
    // Use ES2015 if you need to call the parent method.
    // super.toggleDrawer()
  }

  anotherMethod() {
    // ...
  }
}

customElements.define('fancy-app-drawer', FancyDrawer);

नेटिव एचटीएमएल एलिमेंट को बढ़ाना

मान लें कि आपको एक आकर्षक <button> बनाना है. <button> के काम करने के तरीके और फ़ंक्शन की नकल करने के बजाय, कस्टम एलिमेंट का इस्तेमाल करके मौजूदा एलिमेंट को बेहतर बनाना एक बेहतर विकल्प है.

पहले से मौजूद पसंद के मुताबिक बनाया गया एलिमेंट एक कस्टम एलिमेंट है, जो ब्राउज़र के किसी पहले से मौजूद एचटीएमएल टैग को बड़ा करता है. किसी मौजूदा एलिमेंट को बढ़ाने का मुख्य फ़ायदा यह है कि उसकी सभी सुविधाएं (DOM प्रॉपर्टी, तरीके, सुलभता) हासिल की जा सकती हैं. मौजूदा एचटीएमएल एलिमेंट को बेहतर तरीके से बेहतर बनाने के बजाय, प्रोग्रेसिव वेब ऐप्लिकेशन लिखने का कोई बेहतर तरीका नहीं है.

किसी एलिमेंट को बढ़ाने के लिए, आपको ऐसी क्लास की परिभाषा बनानी होगी जो सही DOM इंटरफ़ेस से इनहेरिट करे. उदाहरण के लिए, <button> को बढ़ाने वाले कस्टम एलिमेंट को HTMLElement के बजाय HTMLButtonElement से इनहेरिट करना होगा. इसी तरह, <img> को बढ़ाने वाले एलिमेंट के लिए HTMLImageElement बढ़ाना ज़रूरी है.

उदाहरण - <button> को बढ़ाया जा रहा है:

// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class FancyButton extends HTMLButtonElement {
  constructor() {
    super(); // always call super() first in the constructor.
    this.addEventListener('click', e => this.drawRipple(e.offsetX, e.offsetY));
  }

  // Material design ripple animation.
  drawRipple(x, y) {
    let div = document.createElement('div');
    div.classList.add('ripple');
    this.appendChild(div);
    div.style.top = `${y - div.clientHeight/2}px`;
    div.style.left = `${x - div.clientWidth/2}px`;
    div.style.backgroundColor = 'currentColor';
    div.classList.add('run');
    div.addEventListener('transitionend', (e) => div.remove());
  }
}

customElements.define('fancy-button', FancyButton, {extends: 'button'});

ध्यान दें कि किसी नेटिव एलिमेंट को एक्सटेंड करते समय, define() को किए जाने वाले कॉल में थोड़ा बदलाव होता है. ज़रूरी तीसरे पैरामीटर से ब्राउज़र को यह पता चलता है कि किस टैग को बढ़ाया जा रहा है. ऐसा इसलिए ज़रूरी है, क्योंकि कई एचटीएमएल टैग एक ही डीओएम इंटरफ़ेस शेयर करते हैं. <section>, <address>, और <em> (अन्य के साथ) सभी HTMLElement को शेयर करते हैं; <q> और <blockquote>, HTMLQuoteElement को शेयर करते हैं; वगैरह... {extends: 'blockquote'} की जानकारी देने से ब्राउज़र को यह पता चलता है कि <q> के बजाय <blockquote> को बनाया जा रहा है. एचटीएमएल के डीओएम इंटरफ़ेस की पूरी सूची के लिए, एचटीएमएल की खास जानकारी देखें.

पसंद के मुताबिक बनाए गए बिल्ट-इन एलिमेंट का इस्तेमाल करने वाले लोग, इसका कई तरह से इस्तेमाल कर सकते हैं. वे नेटिव टैग पर is="" एट्रिब्यूट जोड़कर, इसका एलान कर सकते हैं:

<!-- This <button> is a fancy button. -->
<button is="fancy-button" disabled>Fancy button!</button>

JavaScript में एक इंस्टेंस बनाएं:

// Custom elements overload createElement() to support the is="" attribute.
let button = document.createElement('button', {is: 'fancy-button'});
button.textContent = 'Fancy button!';
button.disabled = true;
document.body.appendChild(button);

या new ऑपरेटर का इस्तेमाल करें:

let button = new FancyButton();
button.textContent = 'Fancy button!';
button.disabled = true;

यहां एक और उदाहरण दिया गया है, जिसमें <img> के बारे में जानकारी दी गई है.

उदाहरण - <img> को बढ़ाया जा रहा है:

customElements.define('bigger-img', class extends Image {
  // Give img default size if users don't specify.
  constructor(width=50, height=50) {
    super(width * 10, height * 10);
  }
}, {extends: 'img'});

उपयोगकर्ता इस कॉम्पोनेंट के लिए यह एलान करते हैं:

<!-- This <img> is a bigger img. -->
<img is="bigger-img" width="15" height="20">

या JavaScript में कोई इंस्टेंस बनाएं:

const BiggerImage = customElements.get('bigger-img');
const image = new BiggerImage(15, 20); // pass constructor values like so.
console.assert(image.width === 150);
console.assert(image.height === 200);

अन्य जानकारी

अनजान एलिमेंट बनाम तय नहीं किए गए कस्टम एलिमेंट

एचटीएमएल ज़्यादा लचीला और सुविधाजनक है. उदाहरण के लिए, किसी पेज पर <randomtagthatdoesntexist> के बारे में एलान करें और ब्राउज़र उसे स्वीकार कर लेगा. नॉन-स्टैंडर्ड टैग काम क्यों करते हैं? इसका जवाब है, एचटीएमएल की खास बातें इसकी अनुमति है. जिन एलिमेंट के बारे में जानकारी नहीं दी गई है उन्हें HTMLUnknownElement के तौर पर पार्स किया जाता है.

कस्टम एलिमेंट के लिए भी ऐसा ही नहीं होता. संभावित कस्टम एलिमेंट को HTMLElement के तौर पर पार्स किया जाता है, अगर उन्हें मान्य नाम ("-" शामिल है) के साथ बनाया गया हो. इसे ऐसे ब्राउज़र में देखा जा सकता है जिसमें कस्टम एलिमेंट काम करते हैं. कंसोल को शुरू करें: Ctrl+Shift+J (या Mac पर Cmd+Opt+J) और कोड की नीचे दी गई लाइन चिपकाएं:

// "tabs" is not a valid custom element name
document.createElement('tabs') instanceof HTMLUnknownElement === true

// "x-tabs" is a valid custom element name
document.createElement('x-tabs') instanceof HTMLElement === true

एपीआई का संदर्भ

customElements ग्लोबल, कस्टम एलिमेंट के साथ काम करने के कारगर तरीकों के बारे में बताता है.

define(tagName, constructor, options)

ब्राउज़र में नया कस्टम एलिमेंट तय करता है.

उदाहरण

customElements.define('my-app', class extends HTMLElement { ... });
customElements.define(
    'fancy-button', class extends HTMLButtonElement { ... }, {extends: 'button'});

get(tagName)

कस्टम एलिमेंट के किसी मान्य टैग का नाम देने से, एलिमेंट के कंस्ट्रक्टर की जानकारी मिलती है. अगर एलिमेंट की कोई परिभाषा रजिस्टर नहीं की गई है, तो undefined दिखाता है.

उदाहरण

let Drawer = customElements.get('app-drawer');
let drawer = new Drawer();

whenDefined(tagName)

ऐसा प्रॉमिस देता है जो कस्टम एलिमेंट तय होने के बाद पूरा होता है. अगर एलिमेंट पहले से तय किया गया है, तो उसे तुरंत ठीक करें. अगर टैग का नाम एक मान्य कस्टम एलिमेंट नाम नहीं है, तो इसे अस्वीकार कर दिया जाता है.

उदाहरण

customElements.whenDefined('app-drawer').then(() => {
  console.log('ready!');
});

इतिहास और ब्राउज़र सहायता

अगर पिछले कुछ सालों से वेब कॉम्पोनेंट को फ़ॉलो किया जा रहा है, तो आपको पता होगा कि Chrome 36+ ने Custom Elements API का ऐसा वर्शन लागू किया है जो customElements.define() के बजाय document.registerElement() का इस्तेमाल करता है. इसे अब स्टैंडर्ड का बंद वर्शन माना जाता है, जिसे v0 कहा जाता है. customElements.define() सबसे नया वर्शन है और इसे ब्राउज़र वेंडर लागू करना शुरू कर रहे हैं. इसका नाम कस्टम एलिमेंट v1 है.

अगर आपको पुराने v0 वर्शन में दिलचस्पी है, तो html5rocks लेख पढ़ें.

ब्राउज़र समर्थन

Chrome 54 (स्थिति), Safari 10.1 (स्थिति), और Firefox 63 (स्थिति) में कस्टम एलिमेंट v1 हैं. एज का डेवलपमेंट शुरू हो गया है.

कस्टम एलिमेंट का पता लगाने की सुविधा के लिए, देखें कि window.customElements मौजूद है या नहीं:

const supportsCustomElementsV1 = 'customElements' in window;

पॉलीफ़िल

जब तक ब्राउज़र पर यह सुविधा पूरी तरह से उपलब्ध नहीं हो जाती, तब तक कस्टम एलिमेंट v1 के लिए स्टैंडअलोन पॉलीफ़िल उपलब्ध रहता है. हालांकि, हमारा सुझाव है कि वेब कॉम्पोनेंट के पॉलीफ़िल को बेहतर तरीके से लोड करने के लिए, webcomponents.js लोडर का इस्तेमाल करें. लोडर, सुविधा की पहचान करने की सुविधा का इस्तेमाल करके, एसिंक्रोनस रूप से सिर्फ़ ब्राउज़र के लिए ज़रूरी पोलीफ़िल को लोड करता है.

इसे इंस्टॉल करें:

npm install --save @webcomponents/webcomponentsjs

इस्तेमाल:

<!-- Use the custom element on the page. -->
<my-element></my-element>

<!-- Load polyfills; note that "loader" will load these async -->
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js" defer></script>

<!-- Load a custom element definitions in `waitFor` and return a promise -->
<script type="module">
  function loadScript(src) {
    return new Promise(function(resolve, reject) {
      const script = document.createElement('script');
      script.src = src;
      script.onload = resolve;
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }

  WebComponents.waitFor(() => {
    // At this point we are guaranteed that all required polyfills have
    // loaded, and can use web components APIs.
    // Next, load element definitions that call `customElements.define`.
    // Note: returning a promise causes the custom elements
    // polyfill to wait until all definitions are loaded and then upgrade
    // the document in one batch, for better performance.
    return loadScript('my-element.js');
  });
</script>

नतीजा

कस्टम एलिमेंट, हमें ब्राउज़र में नए एचटीएमएल टैग तय करने और फिर से इस्तेमाल किए जा सकने वाले कॉम्पोनेंट बनाने के लिए, एक नया टूल देते हैं. उन्हें Shadow DOM और <template> जैसे नए प्लैटफ़ॉर्म के साथ जोड़ें और हमें वेब कॉम्पोनेंट की शानदार फ़ोटो दिखने लगी है:

  • फिर से इस्तेमाल किए जा सकने वाले कॉम्पोनेंट बनाने और बढ़ाने के लिए क्रॉस-ब्राउज़र (वेब स्टैंडर्ड).
  • शुरू करने के लिए, किसी लाइब्रेरी या फ़्रेमवर्क की ज़रूरत नहीं होती. वैनिला JS/एचटीएमएल एफ़टीडब्ल्यू!
  • जाना-पहचाना प्रोग्रामिंग मॉडल देता हो. यह सिर्फ़ DOM/CSS/HTML है.
  • अन्य नई वेब प्लैटफ़ॉर्म सुविधाओं (शैडो DOM, <template>, सीएसएस कस्टम प्रॉपर्टी वगैरह) के साथ अच्छी तरह से काम करता है
  • ब्राउज़र के DevTools के साथ बढ़िया तरीके से इंटिग्रेट किया गया.
  • मौजूदा सुलभता सुविधाओं का फ़ायदा उठाएं.