Javascript und css-Selectoren
Pit
- javascript
Hallo,
wie kann ich aus
<section class="gallery">
<a href="./img/gallery/123.jpg">
<figure id="0">
<img src="./img/gallery/123.jpg"/>
<figcaption>Text Nummer 1</figcaption>
</figure>
</a>
<a href="./img/gallery/234.jpg">
<figure id="1">
<img src="./img/gallery/234.jpg"/>
<figcaption>Text Nummer 2</figcaption>
</figure>
</a>
usw.
</section>
ein Array machen, das indexbasiert alle Figcaptions beinhaltet?
Ich habs über allFigCaption = document.querySelectorAll(".gallery figcaption");
versucht, dann habe ich aber alle Nodes im Srray und viel zu viele Informationen.
Ich hätte aber gerne nur die reinen Texte.
Kann mir da jemand unter die Arme greifen?
Pit
Hallo
Wie du die Elemente selektieren kannst, weißt du ja schon.
Wenn du den textuellen Inhalt eines Elements lesen möchtest, kannst du das mit der Eigenschaft Node.prototype.textContent
machen. Das kann dann so aussehen:
const figcaptions = document.querySelectorAll('.gallery figcaption'),
contents = Array.from(figcaptions, figcaption => figcaption.textContent);
Das ist einfacher, aber im Wesentlichen das Gleiche als wenn du die Methode Array.prototype.map()
verwendest. Wenn es auch in älteren Browsern laufen soll:
var figcaptions = [].slice.call(document.querySelectorAll('.gallery figcaption'));
var contents = figcaptions.map(function(figcaption) {
return figcaption.textContent;
});
Oder mit Schleife:
var figcaptions = document.querySelectorAll('.gallery figcaption'),
contents = [];
for (var index = 0; index < figcaptions.length; index++) {
contents[index] = figcations[index].textContent;
}
Viele Grüße,
Orlok
Hallo Orlok,
Wie du die Elemente selektieren kannst, weißt du ja schon.
Naja, so ein bischen, würde ich sagen 😉
Wenn du den textuellen Inhalt eines Elements lesen möchtest, kannst du das mit der Eigenschaft
Node.prototype.textContent
machen. Das kann dann so aussehen:
Gibt es sowas auch für Image-sources? Oder deshalb nicht, weil src kein Node ist, sondern ein Attribut?
const figcaptions = document.querySelectorAll('.gallery figcaption'), contents = Array.from(figcaptions, figcaption => figcaption.textContent);
Wow, cooles Konstrukt.
Oder mit Schleife:
var figcaptions = document.querySelectorAll('.gallery figcaption'), contents = []; for (var index = 0; index < figcaptions.length; index++) { contents[index] = figcations[index].textContent; }
Das sieht auch fein aus, werde ich jetzt auch mal ausprobieren, nachdem ich Option1 aber schon als für mich perfekt geeignet eingestuft habe.
Dank Dir,
pit
Hallo Orlok,
zu diesem Code hätte ich Fragen.
var figcaptions = [].slice.call(document.querySelectorAll('.gallery figcaption'));
var contents = figcaptions.map(function(figcaption) {
return figcaption.textContent;
});
var contents = Array.prototype.map.call(
document.querySelectorAll('.gallery figcaption'),
function(figcaption) { return figcaption.textContent; }
);
NodeList.prototype.map = NodeList.prototype.map || Array.prototype.map;
Rolf
Hallo Rolf
zu diesem Code hätte ich Fragen.
var figcaptions = [].slice.call(document.querySelectorAll('.gallery figcaption')); var contents = figcaptions.map(function(figcaption) { return figcaption.textContent; });
- Warum [].slice und nicht Array.prototype.slice? Für [].slice muss die JS Engine ein Dummy-Array erzeugen. Wegwerfobjekte belasten den Garbage Collector unnötig. Oder sind die JITs so schlau, dass sie das erkennen?
Du hast recht, man könnte hier Array.prototype.slice
genau so gut oder besser auch ohne den Umweg über Dummy-Array und Prototypen-Kette aufrufen.
Gäbe es Array.from
(gegebenenfalls mit Polyfill) nicht, würde ich der von dir genannten Variante wohl den Vorzug geben.
Allerdings kaum aus Gründen der Performance, denn zum einen gehe ich – auch wenn ich gerade keine Quelle zitieren kann – in der Tat davon aus, dass so etwas wie [].slice.call
von den meisten Engines heute wegoptimiert wird, und zum anderen, selbst wenn nicht, würde das in den allermeisten Fällen wohl im Grundrauschen untergehen.
Der insgesamt relevantere Aspekt ist diesbezüglich wohl eher die Lesbarkeit des Codes. Zwar ist [].slice.call(indexedCollection)
ein sehr häufig verwendetes Muster – und in diesem Bereich etwas erfahreneren Entwicklern und Entwicklerinnen in der Regel gut bekannt, aber besser lesbar ist doch die direkte Referenzierung.
2. Warum überhaupt slice? Das macht ja einfach nur eine Kopie. Die NodeList ist zwar live, aber da JS single-threaded ist, sollte sie sich während des map Durchlaufs nicht ändern. Kann man es nicht direkt so schreiben?
var contents = Array.prototype.map.call( document.querySelectorAll('.gallery figcaption'), function(figcaption) { return figcaption.textContent; } );
Ja, gut gesehen! So kann man es natürlich auch schreiben.
Die Methode Array.prototype.map
ist generisch, das heißt, das Kontextobjekt muss kein Array sein. Das kann auch eine NodeList, eine HTMLCollection, ein arguments
-Objekt oder ein korrekt präpariertes planes Objekt sein.
Wichtig ist nur das Vorhandensein einer length
-Eigenschaft und entsprechender Indizes als Eigenschaftsnamen. Solange hier lediglich abgebildet und nicht innerhalb der Rückruffunktion das DOM verändert wird, ist das unproblematisch.
EDIT: Bei querySelectorAll
übrigens ohnehin, denn die zurückgegebene NodeList ist AFAIS statisch, und nicht live.
- Würde etwas dagegen sprechen, NodeList zu augmentieren? Sowas habe ich heute noch im Büro eingebaut, ich fand es praktisch, node.querySelectorAll.map(...) schreiben zu können.
Das ist wohl nur von Fall zu Fall zu beantworten.
Allgemein ist es keine gute Idee, mit anderen Programmen gemeinsam genutzte Objekte zu verändern, es sei denn, es geht darum standardisierte Funktionen nachzubauen, die von der konkreten Hostumgebung nicht unterstützt werden.
Das Problem ist halt, dass möglicherweise zwei Programme denselben Eigenschaftsnamen nutzen möchten, aber eine (womöglich leicht) unterschiedliche Implementierung im Sinn haben, zum Beispiel f(item, index)
versus f(index, item)
.
Wenn es keine anderen Programme gibt, nicht zu erwarten ist, dass welche hinzukommen, oder man davon ausgehen kann, die Sache im Auge zu behalten, dann spricht nichts dagegen. Also wenn… ;-)
Und bevor man den .map Polyfill aus der MDN abschreibt - kann man einfach das hier machen?
NodeList.prototype.map = NodeList.prototype.map || Array.prototype.map;
Klar. :-) Ich sehe keinen Grund, weshalb das nicht funktionieren sollte. Das Polyfill bräuchte man ja nur dann, wenn Array.prototype.map nicht unterstützt wird, was heute wohl kaum noch vorkommen dürfte.
Viele Grüße,
Orlok
Man sieht auch häufig die Verwenundung des spread-Operators, um Iterierbares in Arrays zu konvertieren:
const elements = [...node.querySelectorAll('.gallery figcaption')]
const contents = elements.map(element => element.textContent)
Nur eine weitere Möglichkeit.
Hallo 1unitedpower
Man sieht auch häufig die Verwenundung des spread-Operators, um Iterierbares in Arrays zu konvertieren:
const elements = [...node.querySelectorAll('.gallery figcaption')]
Nur eine weitere Möglichkeit.
Dann habe ich auch noch eine. :-)
const [...elements] = node.querySelectorAll('.gallery figcaption')
Hier wird die iterierbare NodeList destrukturiert und die Elemente mittels Rest-Syntax an den Bezeichner der Konstante gebunden, wobei ebenfalls ein Array erzeugt wird.
Für diejenigen, die es interessiert, schauen wir uns zunächst mal die Syntax des ersten Ausdrucks etwas genauer an.
const elements = [...node.querySelectorAll('selectors')]
Das könnte man in etwa wiefolgt ableiten, wobei wir aus Gründen der besseren Lesbarkeit einige (viele) Ableitungsschritte auslassen:
<lexical-declaration>
=> <let-or-const> <binding-list>
=> const <binding-list>
=> const <lexical-binding>
=> const <binding-identifier> <initializer>
=> const <identifier> <initializer>
=> const elements <initializer>
=> const elements = <assignment-expression>
=> const elements = <primary-expression>
=> const elements = <array-literal>
=> const elements = [ <element-list> ]
=> const elements = [ <spread-element> ]
=> const elements = [... <assignment-expression> ]
=> const elements = [... <call-expression> ]
=> const elements = [... <member-expression> <arguments> ]
=> const elements = [... <identifier> . <identifier-name> <arguments> ]
=> const elements = [...node. <identifier-name> <arguments> ]
=> const elements = [...node.querySelectorAll( <argument-list> )]
=> const elements = [...node.querySelectorAll( <string-literal> )]
=> const elements = [...node.querySelectorAll('selectors')]
Von besonderem Interesse ist hier natürlich der folgende Ableitungsschritt, der zeigt, dass nach dem spread-Operator aus syntaktischer Sicht im Wesentlichen wirklich irgendein Ausdruck stehen kann. Die besonderen Anforderungen ergeben sich hier aus der Semantik.
=> const elements = [... <assignment-expression> ]
Der Ausdruck nach dem spread-Operator sollte zu einer Referenz auf ein Objekt aufgelöst werden, welches das Iterable Interface implementiert, das also iterierbar ist. Dazu gehört nicht viel, nur das Vorhandensein einer Methode mit einem bestimmten Eigenschaftsnamen, die wenn man sie aufruft, einen sogenannten Iterator zurückgibt.
Der Name der Methode muss das standardmäßig über die Objekteigenschaft Symbol.iterator referenzierbare Symbol @@iterator sein. Das ist eins von mehreren Symbolen, die auf dem eingebauten Funktionsobjekt Symbol hinterlegt sind und die es erlauben, den Ablauf von internen Algorithmen zu beeinflussen, wie eben bei der Iteration von Objekten.
typeof [][Symbol.iterator] // function
Das Codebeispiel oben zeigt, dass für Arrays eine entsprechende Methode definiert ist.
In bestimmten Kontexten, wie etwa bei einer Schleife mit for
und of
oder eben bei der Akkumulation von Arrays mittels spread-Syntax, wird diese Methode aufgerufen und es wird erwartet, dass sie ein Objekt zurückgibt, welches das Iterator Interface implementiert. Dieser Iterator enthält gewissermaßen die Information, wie über das jeweilige Objekt zu iterieren ist, also welche Werte in welcher Reihenfolge auszugeben sind.
Die Minimalanforderung an den Iterator ist, dass er über eine Methode namens next
verfügt, die beim Aufruf ein Objekt mit zwei Eigenschaften zurückgibt, nämlich zum einen die Eigenschaft value
, welche den Wert enthält, der im aktuellen Iterationsschritt ausgegeben werden soll, und zum anderen die Eigenschaft done
, die einen booleschen Wert zurückgibt und die dazu dient mitzuteilen, wann die Iteration beendet werden soll, beispielsweise, weil alle Werte aus dem iterierbaren Objekt ausgegeben wurden.
const nodeList = [element1, element2, element3],
nodeListIterator = nodeList[Symbol.iterator]()
const elements = [
nodeListIterator.next().value, // element1
nodeListIterator.next().value, // element2
nodeListIterator.next().value // element3
]
elements // [element1, element2, element3]
Das Beispiel oben zeigt also, was im Prinzip bei der Auswertung des hier betrachteten Ausdrucks passiert: Auf dem Objekt, das über den Ausdruck nach ...
referenziert worden ist, wird die Methode Symbol.iterator
aufgerufen und auf dem zurückgegebenen Iterator wird solange next
aufgerufen bis alle Werte ausgegeben wurden. Die so gewonnenen Werte wiederum werden dazu verwendet, das neu erstellte Array zu initialisieren.
Schauen wir uns angefangen mit der Syntax nun den zweiten Ausdruck an:
const [...elements] = node.querySelectorAll('selectors')
Dieser Ausdruck lässt sich wiefolgt ableiten, wobei wir uns wieder die meisten Ableitungsschritte sparen, da sie nichts zur Sache tun:
<lexical-declaration>
=> <let-or-const> <binding-list>
=> const <binding-list>
=> const <lexical-binding>
=> const <binding-pattern> <initializer>
=> const <array-binding-pattern> <initializer>
=> const [ <binding-rest-element> ] <initializer>
=> const [... <binding-identifier> ] <initializer>
=> const [... <identifier> ] <initializer>
=> const [...elements] = <assignment-expression>
=> const [...elements] = <call-expression>
=> const [...elements] = <member-expression> <arguments>
=> const [...elements] = <identifier> . <identifier-name> <arguments>
=> const [...elements] = node. <identifier-name> <arguments>
=> const [...elements] = node.querySelectorAll( <argument-list> )
=> const [...elements] = node.querySelectorAll( <string-literal> )
=> const [...elements] = node.querySelectorAll('selectors')
An dieser Stelle interessieren wir uns besonders für den folgenden, aus Gründen der besseren Anschauung zusammengefassten Ableitungsschritt:
=> const [... <binding-identifier> ] = <assignment-expression>
Hierbei handelt es sich um ein Muster zur Destrukturierung von iterierbaren Objekten. Während auf der rechten Seite syntaktisch wieder irgendein Ausdruck stehen kann, muss auf der linken Seite innerhalb der Klammern und nach dem Operator ...
ein valider Bezeichner stehen, der als Zuweisungsziel geeignet ist. Das kann wie im vorliegenden Fall der Bezeichner einer Konstante sein, aber auch eine Variable.
Werden in einem solchen Zuweisungsausdruck ein oder mehrere Bezeichner, durch Komma getrennt, in eckige Klammern gefasst, dann wird so wie oben beschrieben über das Objekt auf der rechten Seite iteriert und die dabei erhaltenen Werte werden einer nach dem anderen an die innerhalb der Klammern notierten Bezeichner gebunden.
Die in der Produktion <binding-rest-element>
zusammengefasste Syntax stellt dabei einen Sonderfall da. Analog zur Notation von Restparametern von Funktionen führt die Notation des Operators ...
vor einem Bezeichner dazu, dass die aus dem destrukturierten Objekt entnommenen Werte, welche nicht zuvor bereits an einen anderen Bezeichner in der Liste gebunden wurden, in ein neu erstelltes Array eingefügt werden, das dann an den nach dem Operator notierten Bezeichner gebunden wird.
Da in dem betrachteten Ausdruck vor ...elements
keine Bezeichner notiert wurden, wird entsprechend der gesamte Inhalt der NodeList in das Rest-Array kopiert.
Viele Grüße,
Orlok
Lieber Orlok,
const elements = [...node.querySelectorAll('.gallery figcaption')]
Nur eine weitere Möglichkeit.
Dann habe ich auch noch eine. :-)
const [...elements] = node.querySelectorAll('.gallery figcaption')
Liebe Grüße,
Felix Riesterer.
Hallo Felix,
const elements = [...node.querySelectorAll('.gallery figcaption')]
Nur eine weitere Möglichkeit.
Dann habe ich auch noch eine. :-)
const [...elements] = node.querySelectorAll('.gallery figcaption')
LG,
CK
Lieber Christian,
da steht npm install --save-dev babel-plugin-transform-es2015-parameters
, was wohl node.js voraussetzt. Was soll ich mit node.js?
Liebe Grüße,
Felix Riesterer.
Hallo Felix,
da steht
npm install --save-dev babel-plugin-transform-es2015-parameters
, was wohl node.js voraussetzt. Was soll ich mit node.js?
Wie wäre es mit mal mehr als nur die drei Zeilen lesen?
Babel ist ein Transpiler. Eine seiner häufigsten Aufgaben ist es, neuere ES-Features zu transpilieren zu Code, der in alten Browsern lauffähig ist.
LG,
CK
Lieber Christian,
Wie wäre es mit mal mehr als nur die drei Zeilen lesen?
das ist ja gerade mein Argument: Ohne Transpiler oder andere Meta-Geschichten, die meinen Code dann wieder zu "Altbackenem" zurückschreiben, ist das im IE nicht nutzbar. Da nützt auch kein Polyfill.
Liebe Grüße,
Felix Riesterer.
Hallo Felix,
Wie wäre es mit mal mehr als nur die drei Zeilen lesen?
das ist ja gerade mein Argument:
Nenene ;-) dein Argument war: „kann man im IE vergessen.“ Und das ist halt nicht wahr.
LG,
CK
Dann habe ich auch noch eine. :-)
const [...elements] = node.querySelectorAll('.gallery figcaption')
Einer geht noch:
const elements = [].concat(node.querySelectorAll('.gallery figcaption'));
Hallo Pit,
in einfachem HTML werden img-Tags übrigens nicht geschlossen, es sind Standalone Tags.
<img src="..."/> brauchst Du nur, wenn Du XHTML schreibst.
Rolf
Hi,
in einfachem HTML werden img-Tags übrigens nicht geschlossen,
doch, img-Tags werden geschlossen - mit dem >
. Bei img-Elementen ist's was anderes.
cu,
Andreas a/k/a MudGuard
Lieber Pit,
<figure id="0"> <figure id="1">
Du verwendest nicht erlaubte Werte für das ID-Attribut. Es muss mit einem Buchstaben beginnen.
Liebe Grüße,
Felix Riesterer.
hallo
<figure id="0"> <figure id="1">
Du verwendest nicht erlaubte Werte für das ID-Attribut. Es muss mit einem Buchstaben beginnen.
Ja du bist nicht der einzige, der die Änderung zwischen HTML 4.1 und html5 verschlafen hat 😉
Lieber beatovich,
Ja du bist nicht der einzige, der die Änderung zwischen HTML 4.1 und html5 verschlafen hat 😉
das darfst Du gerne so vermuten, jedoch hat es gerade im Zusammenhang mit CSS-Selektoren einen Sinn, an dieser Konvention festzuhalten. Gunnar hat das bereits genauer ausgeführt.
Liebe Grüße,
Felix Riesterer.
@@Felix Riesterer
Ja du bist nicht der einzige, der die Änderung zwischen HTML 4.1 und html5 verschlafen hat 😉
das darfst Du gerne so vermuten, jedoch hat es gerade im Zusammenhang mit CSS-Selektoren einen Sinn, an dieser Konvention festzuhalten. Gunnar hat das bereits genauer ausgeführt.
Du sagtest aber nicht „ungünstige Werte für das ID-Attribut“ o.ä., sondern „nicht erlaubte Werte für das ID-Attribut“. Das hat beatovich durchaus zurecht angemeckert.
LLAP 🖖
hallo
Ja du bist nicht der einzige, der die Änderung zwischen HTML 4.1 und html5 verschlafen hat 😉
das darfst Du gerne so vermuten, jedoch hat es gerade im Zusammenhang mit CSS-Selektoren einen Sinn, an dieser Konvention festzuhalten. Gunnar hat das bereits genauer ausgeführt.
Natürlich denke ich bei id's immer an den potentiellen Gebrauch im CSS (wobei ich zunehmend davon Abstand nehme).
[id="..."] ist von mir des öfteren bevorzugt.
Hi Felix,
Du verwendest nicht erlaubte Werte für das ID-Attribut. Es muss mit einem Buchstaben beginnen.
Abgesehen davon, dass die IDs hier eh rausfliegen, ist das, glaube ich, nicht mehr zwingend so, sondern sollte nur noch aus Kompatibilitätsgründen gemacht werden.
Pit
@@Pit
Abgesehen davon, dass die IDs hier eh rausfliegen, ist das, glaube ich, nicht mehr zwingend so
Richtig. Und du hättest das nicht glauben, sondern wissen sollen.
sondern sollte nur noch aus Kompatibilitätsgründen gemacht werden.
Was meinst du mit „Kompatibilitätsgründen“?
Ein Grund, der gegen Ziffern am Anfang von Bezeichnern spricht, ist, dass Bezeichner in CSS nicht mit einer Ziffer beginnen dürfen. Das ist schwer handhabbar.
Insbesondere Selektoren wie #\30 figcaption
– beachte die zwei Leerzeichen[1] hintereinander, die beide eine unterschiediche Funktion haben. Siehe auch die auf das verlinkte Posting folgenden.
LLAP 🖖
Ich hab hier NBSP verwendet, denn bei zwei normalen Leerzeichen wird nur eins angezeigt. Keine Ahnung, wie man Markdown dazu bringt, bei Inline-Code die Leerzeichen nicht zu einem zusammenzufassen. ↩︎
Hi Gunnar,
sondern sollte nur noch aus Kompatibilitätsgründen gemacht werden.
Was meinst du mit „Kompatibilitätsgründen“?
Das meine nicht ich, sondern die verlinkte Seite. Ich denke, sie meinen ältere Browser, die nicht html5-kompatibel sind?
Ein Grund, der gegen Ziffern am Anfang von Bezeichnern spricht, ist, dass Bezeichner in CSS nicht mit einer Ziffer beginnen dürfen. Das ist schwer handhabbar.
Ich finde es auch unproblematisch, es so zu handhaben... insofern, alles gut.
Pit
@@Pit
Ich denke, sie meinen ältere Browser, die nicht html5-kompatibel sind?
Ich denke nicht, dass Abwärtskompatibilität hier ein Problem ist. Die HTML-Spezifikation konnte ja gerade erweitert werden, weil Browser schon immer mit Ziffern beginnenden IDs zurechtkamen, d.h. <div id="0">
/ document.getElementById('0')
funktionierte schon immer™.
document.querySelector('#0')
geht natürlich nicht, weil die führende 0
im Selektor escapet werden muss. Das gilt nicht nur in CSS, sondern allgemein für Selektoren (die aus gutem Grund nicht „CSS-Selektoren“ heißen).
document.querySelector('#\30')
geht aber nicht, denn in JavaScript muss auch das \
escapet werden: document.querySelector('#\\30')
.
LLAP 🖖
Hallo Pit
Was mir gerade noch aufgefallen ist…
<section class="gallery"> <a href="./img/gallery/123.jpg"> <figure id="0"> <img src="./img/gallery/123.jpg"/> <figcaption>Text Nummer 1</figcaption> </figure> </a> <a href="./img/gallery/234.jpg"> <figure id="1"> <img src="./img/gallery/234.jpg"/> <figcaption>Text Nummer 2</figcaption> </figure> </a> usw. </section>
In einem richtigen Dokument sollten die img
-Elemente auf jeden Fall alt
-Attribute haben, auch dann, wenn du eine Bildbeschreibung im figcaption
-Element angibst.
Da muss und soll nicht dasselbe drinstehen wie im figcaption
, aber das Attribut muss vorhanden sein, da sonst meines Wissens in Screenreadern der Pfad mit dem Namen der Bilddatei vorgelesen wird, was eine eher unschöne user experience ist.
Je nach Bild und Beschreibung kann das Attribut auch leer bleiben. – In HTML-Syntax:
<img src="path/to/image.png" alt>
oder im XHTML-Dialekt:
<img src="path/to/image.png" alt=""/>
Viele Grüße,
Orlok
Hallo Pit
Was mir gerade noch aufgefallen ist…
In einem richtigen Dokument sollten die
img
-Elemente auf jeden Fallalt
-Attribute haben, auch dann, wenn du eine Bildbeschreibung imfigcaption
-Element angibst.
Ich weiß, steht auch schon auf der ToDoList. Danke dennoch…
pit