1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
|
---
title: Benutzerdefinierte Elemente
slug: Web/Web_Components/Using_custom_elements
translation_of: Web/Web_Components/Using_custom_elements
---
<div>{{DefaultAPISidebar("Web Components")}}</div>
<p class="summary">Eines der Hauptmerkmale des Web Components Standards ist die Möglichkeit, benutzerdefinierte Elemente (custom elements) zu erstellen, die ihre Funktionalität auf einer HTML-Seite kapseln, anstatt sich mit einem langen, verschachtelten Stapel von Elementen begnügen zu müssen, die zusammen eine benutzerdefinierte Seitenfunktion bereitstellen. Dieser Artikel stellt die Verwendung von benutzerdefinierten HTML-Elementen vor.</p>
<div class="note">
<p><strong>Hinweis</strong>: Benutzerdefinierte Elemente werden standardmäßig in Chrome und Opera unterstützt. Firefox ist sehr weit in der Entwicklung; sie sind derzeit verfügbar, wenn Sie die Einstellungen <code>dom.webcomponents.shadowdom.enabled </code>und <code>dom.webcomponents.customelements.enabled</code> auf <code>true</code> setzen. Die Implementierung von Firefox soll in der Version 60/61 standardmäßig aktiviert werden. Safari unterstützt bisher nur autonome benutzerdefinierte Elemente, und Edge arbeitet auch an einer Implementierung.</p>
</div>
<h2 id="High-Level-Ansicht">High-Level-Ansicht</h2>
<p>Der Controller von benutzerdefinierten Elementen in einem Web-Dokument ist das Objekt {{domxref("CustomElementRegistry")}} - mit diesem Objekt können Sie ein benutzerdefiniertes Element auf der Seite registrieren, Informationen darüber zurückgeben, welche benutzerdefinierten Elemente registriert sind etc.</p>
<p>Um ein benutzerdefiniertes Element auf der Seite zu registrieren, verwenden Sie die Methode {{domxref("CustomElementRegistry.define()")}}. Das sind die Argumente:</p>
<ul>
<li>Ein {{domxref("DOMString")}} repräsentiert den Namen, den Sie dem Element geben. Beachten Sie, dass benutzerdefinierte Elementnamen einen <a href="https://stackoverflow.com/questions/22545621/do-custom-elements-require-a-dash-in-their-name">Bindestrich erfordern</a>; sie können keine einzelnen Wörter sein.</li>
<li>Ein <a href="/en-US/docs/Web/JavaScript/Reference/Classes">Klassenobjekt</a>, das das Verhalten des Elements definiert.</li>
<li>Optional ein Optionsobjekt, das eine <code>extends</code>Eigenschaft enthält, die das eingebaute Element angibt, von dem Ihr Element erbt, falls vorhanden.</li>
</ul>
<p>So können wir z.B. ein benutzerdefiniertes <a href="https://mdn.github.io/web-components-examples/word-count-web-component/">Wortzählelement</a> wie dieses definieren:</p>
<pre class="brush: js">customElements.define('word-count', WordCount, { extends: 'p' });</pre>
<p>Das Element heißt <code>word-count</code>, sein Klassenobjekt ist WordCount, und es erweitert das Element {{htmlelement("p")}}.</p>
<p>Das Klassenobjekt eines benutzerdefinierten Elements wird mit der ES 2015 Standardsyntax einer Klasse geschrieben. Zum Beispiel ist <code>WordCount</code>so aufgebaut:</p>
<pre class="brush: js">class WordCount extends HTMLParagraphElement {
constructor() {
// Always call super first in constructor
super();
// Element functionality written in here
...
}
}</pre>
<p>Dies ist nur ein einfaches Beispiel, aber es gibt noch mehr, was Sie hier tun können. Es ist möglich, spezifische Lifecycle-Callbacks innerhalb des Konstruktors zu definieren, die an bestimmten Stellen im Lebenszyklus des Elements ablaufen. Zum Beispiel wird <code>connectedCallback </code>aufgerufen, wenn das benutzerdefinierte Element zum ersten Mal mit dem DOM des Dokuments verbunden wird, während <code>attributeChangedCallback</code> aufgerufen wird, wenn eines der Attribute des benutzerdefinierten Elements hinzugefügt, entfernt oder geändert wird.</p>
<p>Im untenstehenden Kapitel {{anch("Using the lifecycle callbacks")}} wirst du mehr über Lifecycle-Callbacks erfahren.</p>
<p>Es gibt zwei Arten von benutzerdefinierten Elementen:</p>
<ul>
<li><strong>Autonome benutzerdefinierte Elemente</strong> sind eigenständig, d.h. sie erben nicht von standardisierten HTML-Elementen. Du kannst diese Art von benutzerdefinierten Elementen verwenden, indem du ein HTML-Element mit dem entsprechenden Namen erzeugst, z.B. <code><popup-info></code> oder <code>document.createElement("popup-info")</code>.</li>
<li><strong>Benutzerdefinierte Standardelemente</strong> erben von standardisierten HTML-Elementen. Beim Erstellen eines solchen Elements musst du angeben, von welchem standardisierten HTML-Element geerbt wird (vgl. obige Beispiele). Um ein benutzerdefiniertes Standardelement zu verwenden, erstellst du Objekt des Basiselements mit dem Attribut (oder der Eigenschaft) {{htmlattrxref("is")}}, z.B. <code><p is="word-count"></code> oder <code>document.createElement("p", { is: "word-count" })</code>. </li>
</ul>
<h2 id="Weitere_einfache_Beispiele">Weitere einfache Beispiele</h2>
<p>Wir gehen durch einige einfache Beispiele um etwas detaillierter zu zeigen wie benutzerdefinierte Elemente erzeugt werden. </p>
<h3 id="Autonome_benutzerdefinierte_Elemente"><strong>Autonome benutzerdefinierte Elemente</strong></h3>
<p>Wir betrachten ein Beispiel eines autonomen benutzerdefinierte Elementes —<br>
<code><a href="https://github.com/mdn/web-components-examples/tree/master/popup-info-box-web-component"><popup-info-box></a></code> (siehe hierzu <a href="https://mdn.github.io/web-components-examples/popup-info-box-web-component/">live example</a>).</p>
<p>Dieses Element nimmt ein Icon und einen Textstring entgegen und fügt das Icon in die Seite ein. Erhält das Icon den Focus, wird der Text in einem Popup-Dialog angezeigt um mehr Information anzubieten</p>
<p>Folgendes JavaScript File definiert eine Klasse <code>PopUpInfo</code>, welches das {{domxref("HTMLElement")}} erweitert. Autonome benutzerdefinierte Elemente erweitern nahezu immer das <code>HTMLElement</code>.</p>
<pre class="brush: js">class PopUpInfo extends HTMLElement {
constructor() {
// Always call super first in constructor
super();
// write element functionality in here
...
}
}</pre>
<p>Dieser Code enthält die {{jsxref("Classes.constructor","constructor")}} Definition für diese Klasse, welche immer mit einem Aufruf von <code><a href="/en-US/docs/Web/JavaScript/Reference/Operators/super">super()</a></code> beginnt, damit die korrekte Prototypkette erzeugt wird.</p>
<p>Innerhalb des Konstruktors definieren wir alle Funktionalität welche das Element haben wird wenn von diesem eine Instanz erzeugt wird. </p>
<p>Wir binden eine shadow root an das Element, benutzen DOM-Funktionalität um die interne shadow DOM -Struktur zu erzeugen, welch dann an die shadow root gebunden wird. Abschließend fügen wir der shadow root noch etwas CSS hinzu um diese zu stylen.</p>
<pre class="brush: js">// Create a shadow root
var shadow = this.attachShadow({mode: 'open'});
// Create spans
var wrapper = document.createElement('span');
wrapper.setAttribute('class','wrapper');
var icon = document.createElement('span');
icon.setAttribute('class','icon');
icon.setAttribute('tabindex', 0);
var info = document.createElement('span');
info.setAttribute('class','info');
// Take attribute content and put it inside the info span
var text = this.getAttribute('text');
info.textContent = text;
// Insert icon
var imgUrl;
if(this.hasAttribute('img')) {
imgUrl = this.getAttribute('img');
} else {
imgUrl = 'img/default.png';
}
var img = document.createElement('img');
img.src = imgUrl;
icon.appendChild(img);
// Create some CSS to apply to the shadow dom
var style = document.createElement('style');
style.textContent = '.wrapper {' +
// CSS truncated for brevity
// attach the created elements to the shadow dom
shadow.appendChild(style);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);</pre>
<p>Letztlich registrieren wir unser benutzerdefiniertes Elementen in der <code>CustomElementRegistry</code> mit der Methode <code>define()</code> , in deren Parametern spezifizieren wir den Namen des Elements und dann den Namen der Klasse welche die Funktionalität definiert:</p>
<pre class="brush: js">customElements.define('popup-info', PopUpInfo);</pre>
<p>Jetzt ist unser benutzerdefiniertes Elementen fertig zur Benutzung auf unserer Seite.<br>
In unserem HTML sieht das nun so aus:</p>
<pre class="brush: html"><popup-info img="img/alt.png" text="Your card validation code (CVC)
is an extra security feature — it is the last 3 or 4 numbers on the
back of your card."></pre>
<div class="note">
<p><strong>Note</strong>: Die vollständige <a href="https://github.com/mdn/web-components-examples/blob/master/popup-info-box-web-component/main.js"> JavaScript Quelle </a>.</p>
</div>
<h3 id="Customized_built-in_elements">Customized built-in elements</h3>
<p>Now let's have a look at another customized built in element example — <a href="https://github.com/mdn/web-components-examples/tree/master/expanding-list-web-component">expanding-list</a> (<a href="https://mdn.github.io/web-components-examples/expanding-list-web-component/">see it live also</a>). This turns any unordered list into an expanding/collapsing menu.</p>
<p>First of all, we define our element's class, in the same manner as before:</p>
<pre class="brush: js">class ExpandingList extends HTMLUListElement {
constructor() {
// Always call super first in constructor
super();
// write element functionality in here
...
}
}</pre>
<p>We will not explain the element functionality in any detail here, but you can discover how it works by checking out the source code. The only real difference here is that our element is extending the {{domxref("HTMLUListElement")}} interface, and not {{domxref("HTMLElement")}}. So it has all the characteristics of a {{htmlelement("ul")}} element with the functionality we define built on top, rather than being a standalone element. This is what makes it a customized built-in, rather than an autonomous element.</p>
<p>Next, we register the element using the <code>define()</code> method as before, except that this time it also includes an options object that details what element our custom element inherits from:</p>
<pre class="brush: js">customElements.define('expanding-list', ExpandingList, { extends: "ul" });</pre>
<p>Using the built-in element in a web document also looks somewhat different:</p>
<pre class="brush: html"><ul is="expanding-list">
...
</ul></pre>
<p>You use a <code><ul></code> element as normal, but specify the name of the custom element inside the <code>is</code> attribute.</p>
<div class="note">
<p><strong>Note</strong>: Again, you can see the full <a href="https://github.com/mdn/web-components-examples/blob/master/expanding-list-web-component/main.js">JavaScript source code</a> here.</p>
</div>
<h2 id="Using_the_lifecycle_callbacks">Using the lifecycle callbacks</h2>
<p>You can define several different callbacks inside a custom element's constructor, which fire at different points in the element's lifecycle:</p>
<ul>
<li><code>connectedCallback</code>: Invoked when the custom element is first connected to the document's DOM.</li>
<li><code>disconnectedCallback</code>: Invoked when the custom element is disconnected from the document's DOM.</li>
<li><code>adoptedCallback</code>: Invoked when the custom element is moved to a new document.</li>
<li><code>attributeChangedCallback</code>: Invoked when one of the custom element's attributes is added, removed, or changed.</li>
</ul>
<p>Let's look at an example of these in use. The code below is taken from the <a href="https://github.com/mdn/web-components-examples/tree/master/life-cycle-callbacks">life-cycle-callbacks</a> example (<a href="https://mdn.github.io/web-components-examples/life-cycle-callbacks/">see it running live</a>). This is a trivial example that simply generates a colored square of a fixed size on the page. The custom element looks like this:</p>
<pre class="brush: html"><custom-square l="100" c="red"></custom-square></pre>
<p>The class constructor is really simple — here we attach a shadow DOM to the element, then attach empty {{htmlelement("div")}} and {{htmlelement("style")}} elements to the shadow root:</p>
<pre class="brush: js">var shadow = this.attachShadow({mode: 'open'});
var div = document.createElement('div');
var style = document.createElement('style');
shadow.appendChild(style);
shadow.appendChild(div);</pre>
<p>The key function in this example is <code>updateStyle()</code> — this takes an element, gets its shadow root, finds its <code><style></code> element, and adds {{cssxref("width")}}, {{cssxref("height")}}, and {{cssxref("background-color")}} to the style.</p>
<pre class="brush: js">function updateStyle(elem) {
var shadow = elem.shadowRoot;
var childNodes = shadow.childNodes;
for(var i = 0; i < childNodes.length; i++) {
if(childNodes[i].nodeName === 'STYLE') {
childNodes[i].textContent = 'div {' +
' width: ' + elem.getAttribute('l') + 'px;' +
' height: ' + elem.getAttribute('l') + 'px;' +
' background-color: ' + elem.getAttribute('c');
}
}
}</pre>
<p>The actual updates are all handled by the life cycle callbacks, which are placed inside the constructor. The <code>connectedCallback()</code> runs when the element is added to the DOM — here we run the <code>updateStyle()</code> function to make sure the square is styled as defined in its attributes:</p>
<pre class="brush: js">connectedCallback() {
console.log('Custom square element added to page.');
updateStyle(this);
}</pre>
<p>the <code>disconnectedCallback()</code> and <code>adoptedCallback()</code> callbacks log simple messages to the console to inform us when the element is either removed from the DOM, or moved to a different page:</p>
<pre class="brush: js">disconnectedCallback() {
console.log('Custom square element removed from page.');
}
adoptedCallback() {
console.log('Custom square element moved to new page.');
}</pre>
<p>The <code>attributeChangedCallback()</code> callback is run whenever one of the element's attributes is changed in some way. As you can see from its properties, it is possible to act on attributes individually, looking at their name, and old and new attribute values. In this case however, we are just running the <code>updateStyle()</code> function again to make sure that the square's style is updated as per the new values:</p>
<pre class="brush: js">attributeChangedCallback(name, oldValue, newValue) {
console.log('Custom square element attributes changed.');
updateStyle(this);
}</pre>
<p>Note that to get the <code>attributeChangedCallback()</code> callback to fire when an attribute changes, you have to observe the attributes. This is done by calling the <code>observedAttributes()</code> getter inside custom element class, including inside it a <code>return</code> statement that returns an array containing the names of the attributes you want to observe:</p>
<pre class="brush: js">static get observedAttributes() {return ['w', 'l']; }</pre>
<p>This is placed right at the top of the constructor, in our example.</p>
<div class="note">
<p><strong>Note</strong>: Find the <a href="https://github.com/mdn/web-components-examples/blob/master/life-cycle-callbacks/main.js">full JavaScript source</a> here.</p>
</div>
|