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

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

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

परिचय

ब्राउज़र, वेब ऐप्लिकेशन को व्यवस्थित करने के लिए एक बेहतरीन टूल देता है. इसे एचटीएमएल कहा जाता है. शायद आपने इसके बारे में सुना हो! यह डेक्लेरेटिव, पोर्टेबल, और अच्छी तरह से काम करने वाला है. साथ ही, इसे इस्तेमाल करना आसान है. HTML भले ही बेहतर हो, लेकिन इसकी शब्दावली और इसे बड़ा करने की सुविधा सीमित है. 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 इंटरफ़ेस का हिस्सा बन जाती है. अपने टैग के लिए सार्वजनिक JavaScript API बनाने के लिए, इस क्लास का इस्तेमाल करें.

उदाहरण - <app-drawer> के डीओएम इंटरफ़ेस को तय करना:

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

प्रतिक्रिया वाले कॉलबैक सिंक होते हैं. अगर कोई व्यक्ति आपके एलिमेंट पर el.setAttribute() को कॉल करता है, तो ब्राउज़र तुरंत attributeChangedCallback() को कॉल करेगा. इसी तरह, डीओएम से एलिमेंट हटाए जाने के तुरंत बाद, आपको 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;

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

<div id="my-id" hidden>

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

किसी प्रॉपर्टी को दिखाना तब काम का होता है, जब आपको एलिमेंट के DOM के रिप्रज़ेंटेशन को उसकी 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> पर अन्य एट्रिब्यूट सेट कर रहे हैं. हालांकि, हम यहां ऐसा नहीं कर रहे हैं, लेकिन attributeChangedCallback का इस्तेमाल करके, JS प्रॉपर्टी को उसके एट्रिब्यूट के साथ सिंक किया जा सकता है.

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

प्रोग्रेसिव एन्हैंसमेंट वाला एचटीएमएल

हमने पहले ही जान लिया है कि कस्टम एलिमेंट तय करने के लिए, 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.
});

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

कस्टम एलिमेंट, एलिमेंट कोड में मौजूद डीओएम एपीआई का इस्तेमाल करके, अपना कॉन्टेंट मैनेज कर सकते हैं. इसके लिए, प्रतिक्रियाएं भेजने की सुविधा का इस्तेमाल किया जा सकता है.

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

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 - कोड सैंपल हटा दिया गया है, क्योंकि इसमें इनलाइन इवेंट हैंडलर का इस्तेमाल किया गया था

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

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

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

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

उदाहरण: किसी <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. शैडो डीओएम की मदद से, एलिमेंट का डीओएम एलिमेंट के लिए स्थानीय होता है
  4. शैडो डीओएम की मदद से, एलिमेंट की इंटरनल सीएसएस का दायरा एलिमेंट तक सीमित होता है

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

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

कस्टम एलिमेंट को स्टाइल करना

भले ही आपका एलिमेंट, शैडो डीओएम का इस्तेमाल करके अपनी स्टाइल तय करता हो, फिर भी उपयोगकर्ता अपने पेज से आपके कस्टम एलिमेंट को स्टाइल कर सकते हैं. इन्हें "उपयोगकर्ता की तय की गई स्टाइल" कहा जाता है.

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

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

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

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

उदाहरण - <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)) मेल नहीं खाता.

एलिमेंट एक्सटेंड करना

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

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

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

उदाहरण - <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> के व्यवहार और फ़ंक्शन को दोहराने के बजाय, कस्टम एलिमेंट का इस्तेमाल करके मौजूदा एलिमेंट को बेहतर बनाना बेहतर विकल्प है.

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

किसी एलिमेंट को एक्सटेंड करने के लिए, आपको एक क्लास की परिभाषा बनानी होगी, जो सही डीओएम इंटरफ़ेस से इनहेरिट होती हो. उदाहरण के लिए, <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> बनाया है. एचटीएमएल के DOM इंटरफ़ेस की पूरी सूची के लिए एचटीएमएल खास जानकारी देखें.

कस्टमाइज़ किए गए पहले से मौजूद एलिमेंट का इस्तेमाल, कई तरीकों से किया जा सकता है. वे नेटिव टैग में 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);

अन्य जानकारी

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

HTML काम में लचीला और लचीला है. उदाहरण के लिए, किसी पेज पर <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 और उसके बाद के वर्शन में, कस्टम एलिमेंट एपीआई का एक ऐसा वर्शन लागू किया गया है जो customElements.define() के बजाय document.registerElement() का इस्तेमाल करता है. इसे अब स्टैंडर्ड का पुराना वर्शन माना जाता है, जिसे v0 कहा जाता है. customElements.define() एक नया और लोकप्रिय तरीका है. इसे ब्राउज़र वेंडर लागू करना शुरू कर रहे हैं. इसे कस्टम एलिमेंट v1 कहा जाता है.

अगर आपको पुराने वर्शन 0 के स्पेसिफ़िकेशन के बारे में जानना है, तो html5rocks का लेख पढ़ें.

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

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

कस्टम एलिमेंट का पता लगाने के लिए, window.customElements की मौजूदगी की जांच करें:

const supportsCustomElementsV1 = 'customElements' in window;

Polyfill

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

इसे इंस्टॉल करने के लिए:

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>

नतीजा

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

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