Opprette en Egendefinert Element fra Scratch

0
32

I den siste artikkelen, fikk vi vår hendene skitne med Web Components ved å lage en HTML-mal som er i dokumentet, men ikke gjengis, helt til vi trenger det.

Neste opp, kommer vi til å fortsette vårt forsøk på å opprette et tilpasset element versjon av dialogen komponent nedenfor, som for tiden kun bruker HTMLTemplateElement:

Se Penn
Dialogen med mal med manus av Caleb Williams (@calebdwilliams)
på CodePen.

Så la oss presse fram ved å opprette et tilpasset element som bruker vår mal#dialog-mal element i real-time.

Artikkel-Serien:

  1. En Introduksjon til Web Components
  2. Lage Gjenbrukbare HTML-Maler
  3. Opprette en Egendefinert Element fra Scratch (Dette innlegget)
  4. Omfatter Stil og Struktur med Skygge DOM (Kommer snart!)
  5. Avanserte Verktøy for Web Components (Kommer snart!)

Opprette en egendefinert element

Brød og smør av Web Components er tilpasset elementer. Den customElements API gir oss en vei til å angi egendefinerte HTML-koder som kan brukes i et hvilket som helst dokument som inneholder definere klasse.

Tenk på det som en Reagerer eller Kantete komponent (f.eks. <MyCard />), men uten å Reagere eller Kantete avhengighet. Native tilpassede elementer ser ut som dette: <min-kort></min-kort>. Enda viktigere, tenk på det som en vanlig element som kan brukes i din Reagerer, Kantete, Vue, [sett inn-framework-du er-interessert-i-denne-uken] programmer uten mye oppstyr.

I hovedsak, en tilpasset element består av to deler: et merkenavn og en klasse som utvider innebygde HTMLElement klasse. Den mest grunnleggende versjonen av vår tilpasset element ville se ut som dette:

klasse OneDialog strekker seg HTMLElement {
connectedCallback() {
dette.innerHTML = `<h1> – Hei, Verden!</h1>`;
}
}

customElements.define(‘en-dialog’, OneDialog);

Merk: gjennom et tilpasset element, denne verdien er en referanse til det tilpassede elementet eksempel.

I eksempelet ovenfor, kan vi definert en ny standardbasert HTML-element, <one-dialogboksen></one-dialogboksen>. Det gjør ikke mye… ennå. For nå, ved hjelp av <en-dialog – > – taggen i HTML-dokumentet vil opprette et nytt element med en <h1> – tag-en å lese “Hallo, Verden!”

Vi er definitivt kommer til ønsker noe mer robust, og vi er på hell. I forrige artikkel så vi på å lage en mal for vår dialog og, siden vi vil ha tilgang til denne malen, la oss bruke det i vår tilpasset element. Vi har lagt til en script-taggen i som eksempel for å gjøre noen dialog magi. la oss ta det nå siden vi skal flytte vårt logikk fra HTML-malen inne i tilpasset element klasse.

klasse OneDialog strekker seg HTMLElement {
connectedCallback() {
const mal = – dokument.bürgerliches (“one-dialogboksen’);
const node = – dokument.importNode(mal.innhold, true);
dette.appendChild(node);
}
}

Nå er våre tilpasset element (<en-dialog ->) er definert og nettleseren er instruert til å gjengi innhold i HTML-mal der det tilpassede elementet kalles.

Vår neste trinn er å flytte vår logikk inn i vår komponent klasse.

Tilpasset element lifecycle metoder

Som Reagerer eller Kantete, tilpassede elementer har lifecycle metoder. Du har allerede vært passivt introdusert til connectedCallback, som kalles når våre element blir lagt til DOM.

Den connectedCallback er atskilt fra elementet ‘ s constructor. Mens constructor brukes til å sette opp bare bones av elementet, connectedCallback er vanligvis brukes til å legge til innhold til elementet, setter opp event lyttere eller på annen måte initialisere komponent.

Faktisk, konstruktør kan ikke brukes til å endre eller manipulere elementets egenskaper ved design. Hvis vi skulle lage en ny forekomst av vår dialog med dokumentet.createElement, konstruktør ville bli kalt. En forbruker av element forventer en enkel node med ingen egenskaper eller innhold som er satt inn.

Den createElement funksjonen har ingen alternativer for konfigurering av element som vil bli returnert. Det står til grunn, da, at konstruktøren ikke bør ha muligheten til å endre elementet som det skaper. Som etterlater oss med connectedCallback som stedet å endre våre element.

Med standard innebygde elementer, element er staten er vanligvis reflektert av hvilke attributter som er til stede på elementet og verdiene av disse attributtene. For vårt eksempel, vi kommer til å se på akkurat ett attributt: [open]. For å gjøre dette, trenger vi å se etter endringer som attributt og vi trenger attributeChangedCallback til å gjøre det. Denne andre lifecycle metode som kalles hver gang en av de element constructor ‘ s observedAttributes er oppdatert.

Det høres kanskje skremmende, men syntaksen er ganske enkel:

klasse OneDialog strekker seg HTMLElement {
statisk få observedAttributes() {
tilbake [‘åpne’];
}

attributeChangedCallback(attrName, oldValue, newValue) {
hvis (newValue !== oldValue) {
dette[attrName] = dette.hasAttribute(attrName);
}
}

connectedCallback() {
const mal = – dokument.bürgerliches (“one-dialogboksen’);
const node = – dokument.importNode(mal.innhold, true);
dette.appendChild(node);
}
}

I vårt tilfelle er over, vi bare bryr seg om attributtet er satt eller ikke, bryr vi oss ikke om en verdi (denne er lik HTML5 nødvendig attributtet på data). Når denne egenskapen er oppdatert, vi vil oppdatere element er åpne holderen. En eiendom eksisterer på en JavaScript-objekt, mens en attributt som finnes på en HTMLElement, dette lifecycle metoden hjelper oss til å holde de to i sync.

Vi pakker updater inne i attributeChangedCallback inne i en betinget sjekke for å se om den nye verdien og gamle verdien er lik. Vi gjør dette for å unngå en uendelig løkke inne i vårt program fordi vi senere kommer til å lage en eiendom getter og setter som vil beholde eiendommen og attributter i synkronisere ved å angi elementet er attributt når elementet er eiendom blir oppdatert. Den attributeChangedCallback gjør det omvendte: oppdateringer holderen når attributtet endringer.

Nå, en forfatter kan bruke vår komponent og tilstedeværelse av åpen attributtet vil diktere om ikke dialogen vil være åpen standard. For å gjøre det litt mer dynamisk, og vi kan legge til egendefinerte getters og bidra til vår elementet er åpne eiendom:

klasse OneDialog strekker seg HTMLElement {
statisk få boundAttributes() {
tilbake [‘åpne’];
}

attributeChangedCallback(attrName, oldValue, newValue) {
dette[attrName] = dette.hasAttribute(attrName);
}

connectedCallback() {
const mal = – dokument.bürgerliches (“one-dialogboksen’);
const node = – dokument.importNode(mal.innhold, true);
dette.appendChild(node);
}

få åpne() {
gå tilbake til denne.hasAttribute(‘åpne’);
}

sett åpent(isOpen) {
hvis (isOpen) {
dette.setAttribute(‘åpne’, true);
} else {
dette.removeAttribute(‘åpne’);
}
}
}

Våre getter og setter vil holde åpent attributt (på HTML-element) og eiendom (på DOM objekt) verdier i sync. Legge til åpne-attributtet satt element.åpne for å true og innstilling element.åpne til true vil legge til den åpne attributtet. Vi gjør dette for å sørge for at våre element er staten er reflektert av sine egenskaper. Dette er ikke teknisk nødvendig, men er ansett som en beste praksis for utforming av tilpassede elementer.

Dette betyr uunngåelig føre til en bit av standardteksten, men å lage en abstrakt klasse som holder disse i sync er en ganske triviell oppgave ved repetisjon over den observerte attributt listen og bruke Objektet.defineProperty.

Nå som vi vet om ikke vår dialogboksen åpne, la oss legge til litt logikk for å faktisk gjøre de vise og skjule:

klasse OneDialog strekker seg HTMLElement {
/** Utelatt */
constructor() {
super();
dette.i nærheten = dette.lukk.bind(this);
}

sett åpent(isOpen) {
dette.querySelector(‘.wrapper’).classList.veksle(‘åpne’, isOpen);
dette.querySelector(‘.wrapper’).setAttribute(‘aria-skjult’, !isOpen);
hvis (isOpen) {
dette._wasFocused = – dokument.activeElement;
dette.setAttribute(‘åpne’, “);
dokumentet.addEventListener(‘keydown’, dette._watchEscape);
dette.fokus();
dette.querySelector(‘knappen’).fokus();
} else {
dette._wasFocused && dette._wasFocused.fokus && dette._wasFocused.fokus();
dette.removeAttribute(‘åpne’);
dokumentet.removeEventListener(‘keydown’, dette._watchEscape);
dette.close();
}
}

close() {
hvis (denne.åpen !== false) {
dette.åpne = false;
}
const closeEvent = new CustomEvent (‘- dialogen lukket’);
dette.dispatchEvent(closeEvent);
}

_watchEscape(hendelse) {
hvis (event.tast === ‘Escape’) {
dette.close();
}
}
}

Det er mye å gå på her, men la oss gå gjennom det. Det første vi gjør er å ta våre pakker og slå .åpen klasse basert på isOpen. For å holde våre element tilgjengelig, vi trenger å skifte aria-skjulte attributtet som godt.

Hvis dialogboksen åpne, så vi ønsker å lagre en referanse til tidligere fokusert element. Dette er å gjøre rede for tilgjengelighetskrav. Vi har også legge til en keydown lytter til dokument kalt watchEscape at vi har bundet til element er dette i konstruktøren i et mønster som ligner på hvordan Reagerer metoden håndterer samtalene i klassen komponenter.

Vi gjør dette ikke bare for å sikre riktig bindende for denne.nært, men også fordi Funksjonen.prototypen.bind returnerer en instans av funksjonen med bundet kaller stedet. Ved å lagre en referanse til den nylig bundet metode i constructor, vi er i stand til å ta deretter ut hendelse når dialogboksen er frakoblet (mer om det i et øyeblikk). Vi ender opp ved å fokusere på våre element og innstilling fokus på riktig element i vår skygge rot.

Vi lager også en fin liten utility metode for å lukke våre dialogboksen at utsendelser en egendefinert hendelse varsling noen lytteren at dialogen har vært lukket.

Hvis elementet er lukket (dvs. !åpne), vi sjekker for å sørge for dette._wasFocused eiendom er definert og har fokus metode og ringe for å gå tilbake brukerens fokus tilbake til vanlig DOM. Da vi fjerne våre arrangement lytteren for å unngå eventuelle minne lekkasjer.

Snakker om å rydde opp etter oss selv, som tar oss med til nok et livssyklus-metode: disconnectedCallback. Den disconnectedCallback er den inverse av den connectedCallback i at metoden kalles når elementet er fjernet fra DOM og gjør oss i stand til å rydde opp i alle tilfelle lyttere eller MutationObservers knyttet til våre element.

Det bare skjer, slik at vi har noen flere arrangement lyttere til å koble opp:

klasse OneDialog strekker seg HTMLElement {
/** Utelatt */

connectedCallback() {
dette.querySelector(‘knappen’).addEventListener(‘click’, dette.lukk);
dette.querySelector(‘.overlay’).addEventListener(‘click’, dette.lukk);
}

disconnectedCallback() {
dette.querySelector(‘knappen’).removeEventListener(‘click’, dette.lukk);
dette.querySelector(‘.overlay’).removeEventListener(‘click’, dette.lukk);
}
}

Nå har vi en velfungerende, for det meste tilgjengelig dialog element. Det er et par biter av polsk vi kan gjøre, som å fange fokus på elementet, men det er utenfor rammen av det vi prøver å lære her.

Det er en mer lifecycle metode som ikke gjelder for våre element, adoptedCallback, som fyrer av når elementet er adoptert inn i en annen del av DOM.

I følgende eksempel, du vil nå se at vår mal element er å bli konsumert av en standard <en-dialog – > – element.

Se Penn
Dialogboksen for eksempel ved hjelp av malen ved Caleb Williams (@calebdwilliams)
på CodePen.

En annen ting: ikke-eit presentasjonsproblem komponenter

<One-mal> vi har laget så langt er en typisk tilpasset element i at det inneholder markup og atferd som blir satt inn i dokumentet når elementet er inkludert. Imidlertid, ikke alle elementer som må gjengi visuelt. I den Reagerer økosystem komponenter er ofte brukt til å administrere programmet staten eller noen andre store funksjonalitet, som <Leverandør /> reagere-redux.

La oss tenk for et øyeblikk at våre komponenten er en del av en serie dialoger i en arbeidsflyt. Som en dialog er lukket, er det neste man bør åpne. Vi kunne lage en wrapper-komponent som lytter for vår dialog-lukket arrangement og utvikler seg gjennom arbeidsflyt.

klasse DialogWorkflow strekker seg HTMLElement {
connectedCallback() {
dette._onDialogClosed = dette._onDialogClosed.bind(this);
dette.addEventListener (‘- dialogen lukket”._onDialogClosed);
}

få dialoger() {
tilbake Utvalg.fra(denne.querySelectorAll (“one-dialogboksen’));
}

_onDialogClosed(hendelse) {
const dialogClosed = event.målet;
const nextIndex = dette.dialoger.indexOf(dialogClosed);
hvis (nextIndex !== -1) {
dette.dialoger[nextIndex].åpne = true;
}
}
}

Dette elementet ikke har noen eit presentasjonsproblem logikk, men fungerer som en kontroller for application state. Med en liten innsats, vi kunne gjenskape en Redux-lignende tilstand management system ved hjelp av ingenting, men en tilpasset element som kan administrere et helt program stat i det samme som Reagerer er Redux wrapper gjør.

Det er en dypere titt på egendefinerte elementer

Nå har vi en ganske god forståelse av tilpassede elementer og vår dialog begynner å komme sammen. Men det har fortsatt noen problemer.

Legg merke til at vi har måttet legge til litt CSS for å restyle den dialogboks-knappen på siden vår elementets stiler er i konflikt med resten av siden. Samtidig som vi kunne benytte navngi strategier (som BEM) for å sikre våre stiler vil ikke skape konflikter med andre komponenter, det er en mer miljøvennlig måte å isolere stiler. Spoiler! Det er skygge DOM, og det er det vi skal se på i neste del av denne serien på Web-Komponenter.

En annen ting vi trenger å gjøre er å definere en ny mal for hver komponent eller finne noen måte å slå maler for vår dialog. Som det står, det kan bare være én dialogboksen skriv per side fordi mal som den bruker må alltid være til stede. Så enten vi trenger noen måte å injisere dynamisk innhold eller en måte å bytte maler.

I den neste artikkelen vil vi se på måter å øke brukbarheten av <en-dialog – > – element vi nettopp har opprettet ved å kombinere stil og innhold innkapsling ved hjelp av skyggen DOM.

Artikkel-Serien:

  1. En Introduksjon til Web Components
  2. Lage Gjenbrukbare HTML-Maler
  3. Opprette en Egendefinert Element fra Scratch (Dette innlegget)
  4. Omfatter Stil og Struktur med Skygge DOM (Kommer snart!)
  5. Avanserte Verktøy for Web Components (Kommer snart!)