Introduction
K345.dElement() ist eine Funktion, mit deren Hilfe das Erzeugen aufwendiger HTML-Element-Strukturen vereinfacht werden soll. Dabei werden diverse Unzulänglichkeiten als auch einige Javascript-Eigenheiten wie z.B. die erforderliche Änderung bestimmter Attribut-Namen automatisch berücksichtigt.
Node|Fragment reference = K345.dElement(Object declaration);
Die Funktion dAppend() übernimmt zusätzlich noch das automatische Einhängen der erzeugten Elemente an ein vorhandenes Element.
Node|Fragment reference = K345.dAppend(
String|Node target id|reference|query, Object declaration [, Const mode_flag]
);
dElement() / dAppend() gibt einen DOM-Element-Baum oder ein documentFragment mit den erzeugten Elementen zurück. Dieser Wert kann mit DOM-Methoden (wie zum Beispiel appendChild, insertBefore oder ähnliches) direkt verwendet werden.
dElement überprüft nicht[1] die Sinnhaftigkeit der Elementverschachtelung; unsinniger Code wie <span><ul><p> (hier als HTML-Repräsentation) ist daher möglich. Wie der mit dElement erzeugte ungültige Element-Baum verarbeitet wird, hängt allein vom verwendeten Browser ab.
[1] Wenn Elementen, die keinen Inhalt haben dürfen (z.B. img, input, hr, br, meta,…) ein Inhalt zugewiesen wird, wirft dElement einen Fehler
Background
Es gibt verschiedene Möglichkeiten, Elemente und Elementstrukturen zu erzeugen und in ein HTML-Dokument einzufügen; alle mit Vor- und Nachteilen.
innerHTML
Die (vor allem bei Anfängern) wahrscheinlich bekannteste Methode ist, eine HTML-Zeichenkette zu erzeugen und diese dann einem Element zuzuweisen. Dies ist für einfache Elementstrukturen schnell erzeugt und relativ übersichtlich. Sobald die zu erzeugenden Strukturen jedoch komplexer werden, ergeben sich diverse Probleme:
- Man muß sich selbst um korrekte Elementschreibweise (schließende Tags, optionale schließende Tags, leere Elemente; je nach gewähltem DOCTYPE) kümmern.
- mit zunehmender Komplexität wird es immer schwieriger, die öffnenden / schließenden Element-Tags richtig zuzuordnen, was insbesondere bei nachträglichem Ändern dazu führen kann, daß an der falschen Stelle geändert wird.
- Beim Einfügen variabler Werte muß die Zeichenkette vielfach unterbrochen und mit dem +-Operator wieder zusammengefügt werden.
- Es ergeben sich Probleme, wenn mehrere Typen Anführungszeichen benötigt werden, hier ist dann korrektes Escaping notwendig, um Syntax-Fehler zu vermeiden.
- Zum Anhängen an bestehende Dokumente oder größere Dokument-Teile (bspw.
document.body.innerHTML += '<div>…</div>') ist innerHTML sehr ungeeignet, da der gesamte Element-Baum zu einer Zeichenkette serialisiert werden muß, dann mit dem neuen Code zusammegefügt und danach wieder zu einem Elementbaum zurückgewandelt werden muß. - Beim Serialisieren und Zurückwandeln können Event-Handler unwirksam werden, die bspw. mit addEventListener() zugewiesen wurden, da alle Elementreferenzen neu erzeugt werden und somit die ursprünglichen Referenzen der Handler ungültig sind.
DOM methods
Bei der Erzeugung von Elementen mit DOM-Methoden (createElement, createTextNode, appendChild, insertBefore u.v.m) gibt es einige Dinge zu beachten; insbesondere die korrekte Verschachtelung der erzeugten Elemente die bei Fehlern dazu führen kann, daß die Erzeugung fehlschlägt und / oder nicht das gewünschte Ergebnis erzeugt wird.
- Bei komplexeren Strukturen fallen viele Variablen und viel Schreibarbeit an.
- Man muß genau aufpassen, welches erzeugte Element an welches Andere angehängt werden muß.
- Die Syntax einiger Methoden ist relativ umständlich, beispielsweise bei insertBefore().
- Einige HTML-Attributnamen erfordern bei direkter Zuweisung (d.h. wenn nicht setAttribute() genutzt wird) einen alternativen Namen oder in bestimmten Browsern eine bestimmte Schreibweise des Names (camelCase)
dElement / dAppend
Erzeugt einen Element-Baum unter bevorzugter Verwendung der DOM-Methoden, wobei diverse Browser-Bugs und sonstige Probleme berücksichtigt werden (wenn auch bei Weitem nicht alle; das kann ich im Alleingang nicht bewältigen) und auch Attribut-Namen automatisch angepasst werden. Alle Attribut-Namen können direkt als Schlüsselwort in der Deklaration verwendet werden (siehe auch Abschnitt Syntax).
- Verschachtelte Syntax kann gegebenenfalls verwirrend wirken (kann durch Aufteilen in verschiedene Teilobjekte vermieden / gemindert werden).
Aufbau von Text, bei dem (mehrfach) einzelne Wörter / Sätze / Abschnitte in Elemente eingeschlossen werden sollen, ist aufwendig. (hier ist es eventuell einfacher, über das spezielle Schlüsselwort html entsprechendes Markup als Zeichenkette zu übergeben.Überholt. Einfachere Verwendung ist jetzt durch Übergabe als Array oder durch mehrere text_… Schlüsselwörter möglich (siehe auch Multi-Schlüsselwörter).- dElement muß bei Änderungen / Erweiterungen der Spezifikationen sowie eventueller Inkompatibilitäten aktualisiert werden (wie fast jede externe Ressource).
comparing methods
Nun der Unterschied zwischen den Methoden anhand eines Beispiels, bei dem eine relativ einfache Element-Struktur erzeugt wird, welche variable Werte enthält.
var link_href = "http://example.org/home.html",
img_w = 200,
img_src = "/res/arrow.gif";Zur Erzeugung von folgenden HTML-Elementen und Einhängen an ein (im fiktiven Element-Baum bereits vorhandenes) Element mit der ID »my_element« …
<!-- link with image and some text -->
<a href="http://example.org/home.html" title="link to new page">
<span><img src="/res/arrow.gif" width="200" alt="an arrow">some text</span>
</a>… wäre bei Verwendung der DOM-Methoden folgendes Javascript zu schreiben:
// create link
var my_link = document.createElement("a");
my_link.href = link_href;
my_link.title = "link to new page";
// create image
var my_img = document.createElement("img");
my_img.src = img_src;
my_img.width = img_w;
my_img.alt = "an arrow";
// create text node
var my_span = document.createElement("span");
var my_text = document.createTextNode("some text");
// append
my_span.appendChild(my_img);
my_span.appendChild(my_text);
my_link.appendChild(my_span);
document.getElementById("my_element").appendChild(my_link);und bei Verwendung von innerHTML:
document.getElementById("my_element").innerHTML = '<a href="' + link_href +
'" title="link to new page"><span><img src="' + img_src + '" width="' +
img_w + '" alt="an arrow">some text</span></a>';Dies ist bei einer so geringen Menge erzeugter Elemente zwar noch halbwegs überschaubar, allerdings muß man schon hier genau aufpassen, um die Elemente / Texte korrekt wie gewünscht ineinander zu verschachteln bzw. bei innerHTML die verschiedenen Anführungszeichen nicht zu verwechseln und keine Maskierung oder schließende Tags zu vergessen.
Daher habe ich eine Funktion entwickelt, die ein strukturiert geschriebenes, verschachteltes Objekt-Literal in einen DOM-Elementbaum umwandelt. Das obige Beispiel könnte[2] folgendermaßen geschrieben werden:
var def = {
element: "a", href: link_href, title: "link to new page",
child: { element: "span", child: [
{ element: "img", src: img_src, width: img_w, alt: "an arrow" },
{ text: "some text" }
]}
};
var my_link = K345.dElement(def);
document.getElementById("my_element").appendChild(my_link);Zur Verdeutlichung des Aufbaus der Deklarations-Syntax folgt nun der gleiche Code in einer besonders anschaulichen Schreibweise[2].
var def = { // start of "a" element declaration
element: "a", // keyword "element" sets element name
href: link_href, // variable
title: "link to new page",
child: { // new object literal because "a" has only one child element "span"
element: "span",
child: [ // use array literal, because "span" has two sibling child elements
{ // first child element: object literal for element "img"
element: "img",
src: img_src, // variable
width: img_w, // variable
alt: "an arrow"
},
{ // second child element: object literal for text
text: "some text"
}
] // end of "span" child sibling declaration
} // end of "a" child element declaration
}; // end of "a" element declaration
var my_link = K345.dElement(def); // create elements. "my_link" is reference to "a"
document.getElementById("my_element").appendChild(my_link);var def = {
element: "a",
href: link_href,
title: "link to new page",
child: {
element: "span",
child: [
{
element: "img",
src: img_src,
width: img_w,
alt: "an arrow"
},
{
text: "some text"
}
]
}
};
var my_link = K345.dElement(def);
document.getElementById("my_element").appendChild(my_link);// using multi keyword 'child_*'
var def = {
element: "a", href: link_href, title: "link to new page",
child: { element: "span",
child_arrowimg: { element: "img", src: img_src, width: img_w, alt: "an arrow" },
child_mytext: "some text"
}
};
var my_link = K345.dElement(def);
document.getElementById("my_element").appendChild(my_link);Um eine verschachtelte Struktur zu vereinfachen und / oder die Anzahl Verschachtelungsebenen zu verringern, kann man die Deklaration auch aus mehreren Teilobjekten erstellen. (das ist für dieses konkrete Beispiel eigentlich übertrieben aufwendig und eher unübersichtlich; es soll nur das Prinzip darstellen)
// same code as above
// create image declaration
var def_img = {
element: "img", src: img_src, width: img_w, alt: "an arrow"
};
// text declaration
var def_txt = {text: "some text"};
// span element declaration - includes image and text declarations as children
var def_span = {
element: 'span', child: [def_img, def_txt]
};
// a element declaration - includes child span
var def_a = {
element: "a", href: link_href, title: "link to new page", child: def_span
};
var my_link = K345.dElement(def_a); // create all elements
document.getElementById("my_element").appendChild(my_link);Bei einfachen Zeichenketten ist es nicht erforderlich, Objekte zu erzeugen {text: 'foo'}, {text: 'bar'}, sondern die Zeichenkette kann direkt übergeben werden und dElement erzeugt textNode. Gleiches gilt für mehrer Zeichenketten als Array ["foo", "bar", "baz"]
[2] Für Javascript ist die Verwendung von Einrückungen oder Zeilenumbrüchen eines Objekt-Literals unerheblich, man könnte auch alles ohne Umbrüche und Leerzeichen in eine einzige Zeile schreiben, solange die Regeln für Literal-Schreibweise beachtet werden
Syntax
- The rules of object literal notation apply.
All keywords are to be written in lowercase.- Eine Baum-Deklaration besteht aus beliebig vielen Teil-Deklarationen, welche parallel und / oder verschachtelt auftreten können.
- Eine weitere Verschachtelungs-Ebene wird durch das Schlüsselwort child eingeleitet. Als Wert wird eine einzelne Objekt-Deklaration oder ein Array mit Objekt-Deklarationen notiert; alternativ ist auch die Verwendung mehrerer child_... Schlüsselwörter möglich (siehe multi). Wird als Wert eine Zeichenkette zugewiesen, wird ein Textknoten erzeugt
- Every (sub-)declaration is another valid object containing one or more »key: -value« pairs
- Folgende Schlüsselwörter haben eine besondere Bedeutung:
- element
- child
- loop
- loopdeep
- text
- html
- clone
- clonetop
- attribute (attr, attrib)
- elrefs
- collect
- event
- setif
- setifelse
- condition (cond)
- style
- comment (comm)
Während cond, comm, attr und attrib Kurzformen der entsprechenden Schlüsselwörter sind, haben clone und clonetop bzw. loop und loopdeep jeweils unterschiedliche Funktion. - Schlüsselwörter, die in Javascript selbst als Schlüsselwort oder reserviertes Wort dienen (z.B. for, class usw), müssen als Zeichenkette[3] notiert werden, alle Anderen können auch –wie in den Beispielen– direkt verwendet werden.
- Mindestens eines der Schlüsselwörter element, html, clone, clonetop, text, comment muß in jeder Teil-Deklaration vorhanden sein.
- Soll ein Element mehrere gleichberechtigte Kind-Elemente haben, so sind die einzelnen Teildeklarationen in einem Array zu übergeben (oder mit mehreren child_... Elementen)
[3] Eingeschlossen in die in Javascript zulässigen Zeichenketten-Begrenzungzeichen (' oder ")
Special keywords
- element
- Tag name of the element to be created (string)
extended syntax - text (multi, array)
- create simple textNode element, no further nesting
- html (multi, array)
- create elements from HTML string (like innerHTML)
- child (multi, array)
- Start of a new (sub)declaration; the element(s) will be attached to the parent element as children.
Expects:- a new element declaration
- an array of element declarations for multiple equitable child elements
- an element or DOM tree reference
- a string, if the only child is text (text keyword should be preferred)
- event (multi, array)
- Attach an event to the current element. Expects an object containing the properties function and arguments (or: func / args)
- clone, clonetop
- clones a node element or tree. When using the keyword clonetop, only the element itself is cloned; when using clone instead, the element and all child nodes are cloned.
- Important: If the element and / or it's children to be cloned has an id property or attribute, the id has to be changed manually, if both clone and original element are to be used at the same time in the document. dElement does not change or remove the id of the clone!
- attribute, attrib, attr (multi, array)
- Enforces creation of an attribute node using setAttribute() instead of setting the object property directly. Expects an object containing properties name and value. The usage of attribute should be limited to some problematic cases[4] ; otherwise the property should be set directly.
- elrefs
- Collects all element references containing id or name property in a / several array(s) and/or object(s). These references are instantly available as property of the given object.
- collect (multi)
- Collects all element references containing this keyword in a / several array(s) and / or object(s)
- setif (multi)
- A property will be set only if a condition is fulfilled. Expects an object containing the properties name, value and condition (or cond)
- setifelse (multi)
- A property value will be set to one of two values, depending on whether a condition is fulfilled. Expects an object containing the properties name, value_true, value_false and condition (or cond) experimental feature[5]
- condition, cond
- create / insert element(s) only if a condition is fulfilled
- loop, loopdeep, loopstop
- create multiple similar element declarations
- comment, comm (multi, array)
- create comment node from string
- init
- The init property takes a function reference as value. The referred function will be called after the element tree was created.experimental feature [5]
- style (array)
- set CSS properties. Expects a string of property / value pairs, just like in CSS itself. (e.g.
style: 'color:green; margin-left:3em').
[4] Setting certain element properties may fail in some old(er) browsers or may trigger browser bugs and requires usage of attribute instead.
[5] This feature is still under development and / or not fully tested. It should work but may cause problems in some cases.
multi
All keywords with label multi can be used multiple times within a declaration. Since JavaScript doesn't allow multiple usage of the same property name (either an error is thrown or former values will be overwritten) dElement can handle a special scheme: the keyword, followed by an underscore, followed by a freely chosen alphanumeric identifier, which has to be unique within this declaration.
Examples: text_descript, child_list1, event_change4, attribute_whatever55
array
All keywords with label array can not only handle their expected value type (like string, object, number, reference etc.) but also an array of the corresponding type.
Extended syntax for element
Es ist möglich, Element-ID, Klassen-Namen – sowie bei Elementen, die dies unterstützen, auch Typ, Wert und Name – alternativ zu den entsprechenden Schlüsselwörtern und Attributen id, className, type, value, name direkt im Schlüsselwort element anzugeben. Hierzu wird eine von CSS inspirierte Syntax benutzt.
| Zeichen | Bedeutung | Attribut |
|---|---|---|
| # | Element-ID | id |
| . | Element-Klasse | className |
| @ | Element-Typ | type |
| ~ | Element-Name | name |
| = | Element-Wert | value |
| $ | tag name des Elements | |
Um die einzelnen Bestandteile einfacher lesbar zu gestalten, ist es erlaubt, die einzelnen Bestandteile durch Leerzeichen zu trennen z.B. 'input @button .mystuff #myid'
Die Reihenfolge hinter dem Element-Namen ist beliebig (siehe Beispiel 3). Eine Id, ein Typ, ein Wert oder ein Name darf jeweils nur ein Mal verwendet werden, Klassen-Namen dürfen mehrfach verwendet werden.
Der tag name des Elements wird als erster Wert der Zeichenkette erwartet (siehe Beispiele) oder aber mit $ eingeleitet
// ==== example 1: ID ====
// default
{element: 'div', id: 'foo-bar'};
{element: 'input', type: 'checkbox'};
{element: 'select', name: 'baz'};
// extended syntax
{element: 'div#foo-bar'};
{element: 'input@checkbox'};
{element: 'select~baz'};
// ==== example 2: CLASSES ====
// default
{element: 'div', className: 'bar'};
{element: 'div', className: 'bar baz'};
// extended syntax
{element: 'div.bar'};
{element: 'div.bar.baz'};
// ==== example 3: COMBINED ====
// default
{element: 'div', className: 'bar baz', id: 'foo'};
{element: 'input', type: 'radio', name: 'foo', id: 'bar'};
// extended syntax
{element: 'div#foo.bar.baz'};
{element: 'input@radio#bar~foo'};
// order of class names and id doesn't matter
{element: 'div.bar#foo.baz'};Um die Verarbeitung möglichst einfach und schnell zu halten, ist der Bereich der erlaubten Zeichen eingeschränkt; es darf unter Anderem keines der jeweils anderen einleitenden Zeichen innerhalb eines Wertes verwendet werden, selbst wenn dies prinzipiell nach den HTML-Regeln erlaubt wäre (Ausnahme: value). In einem solchen Fall sollte weiterhin das jeweilige Schlüsselwort id, className, type, name verwendet werden.
Wird ein Attribut sowohl als Schlüsselwort wie auch in der erweiterten Syntax angegeben, verwendet dElement nur den Wert der erweiterten Syntax (Ausnahme: Die Eigenschaft className wird mit den Werten der erweiterten Syntax kombiniert).
Ein value-Attribut kann unter Anderem auch die Zeichen # . ~ @ enthalten. Eine sichere automatische Unterscheidung, ob beim Auftreten eines dieser Zeichen ein weiteres Attribut eingeleitet werden soll oder ob das Zeichen zum value-Attribut gehört, ist nicht ohne grösseren Aufwand möglich. Daher gibt es folgende Möglichkeiten:
- Die Deklaration des value Wertes ans Ende setzen
- Die Deklaration durch das Steuerzeichen 'US' (Unit separator; hexadezimal: 0x1F) beenden. In Javascript kann dieses Zeichen innerhalb von Zeichenketten als
\x1Fgeschrieben werden.
// at end of declaration
var el = K345.dElement({
element: 'button @button .myClass =a #hashtag and mail@example.org',
text: 'hello'
});
// using control char 'US'
var el = K345.dElement({
element: 'button @button =a #hashtag and mail@example.org\x1F .myClass',
text: 'hello'
});<button type="button" class="myClass" value="a #hashtag and mail@example.org">
hello
</button>passing a HTML markup string
Mit dem Schlüsselwort html kann eine Zeichenkette mit HTML übergeben werden, die dann als Element(baum) eingefügt wird.
var new_el = K345.dElement({
element: 'p',
html: '<span class="foobar">Hallo bla bla <u>Wichtig</u></span>'
});
WICHTIG Niemals HTML-Zeichenketten aus ungeprüften / unsicheren Quellen verwenden. Externe Scripts können per XSS ausgeführt werden.
Wird das erzeugte Dokument als application/xhtml+xml ausgeliefert, so muß die notierte HTML Zeichenkette valide sein und (fast) alle named entities müssen durch die korrespondierenden numerischen Versionen ersetzt werden; z.B. muß durch   oder   ersetzt werden. Erlaubt sind: > < & " '
Set declaration on condition
Die Teildeklaration (sowie deren Unter-Deklarationen), in der das condition-Schlüsselwort (alternativ: cond) vorkommt, wird nur dann eingefügt, wenn die Bedingung erfüllt ist. Es ist programmatisch zu beachten, daß eine nicht erfüllte Bedingung in der obersten Deklarations-Ebene dazu führt, daß dElement() / dAppend() null zurückgibt.
var f = 3, xyz;
// condition true [3 > 2]
// xyz is a p-element with child element (<p><span>Hello</span></p>)
xyz = K345.dElement({
element: 'p',
child: { element: 'span', text: 'Hello', cond: f > 2 }
});
// condition false [3 === 2]
// xyz is a empty p-element (<p></p>)
xyz = K345.dElement({
element: 'p',
child: { element: 'span', text: 'Hello', cond: f === 2 }
});
// top level, condition is false
// xyz is 'null
xyz = K345.dElement({
element: 'div',
cond: 2 === 3,
child: {element: 'strong', text: 'bold'}
});Set property on condition
Über das Schlüsselwort setif kann eine Eigenschaft nur dann gesetzt werden, wenn eine bestimmte Bedingung erfüllt ist. setif erwartet als Wert ein Objekt mit den folgenden drei Eigenschaften:
- name ist der Eigenschaftsname
- value ist der Wert, welcher der Eigenschaft zugewiesen wird
- condition (alternativ: cond) ist die Bedingung, die erfüllt sein muß
Ist der Wert der Eigenschaft name die Zeichenkette "child" sowie value ein Objekt / Array / Element, wird dieses an das aktuelle Element angehängt, wenn die Bedingung erfüllt ist.
Das Schlüsselwort setif ist multi-fähig
// className will be set only if i === 12
var div_foo = K345.dElement({
element: 'div',
id: 'foo',
setif: {
name: 'className',
value: 'marked',
condition: (i === 12)
}
});
// special case "child"
var ch = {element: 'strong', text: 'appended'};
var div_bar = K345.dElement({
element: 'div',
id: 'bar',
setif: {
name: 'child',
value: ch,
condition: (i % 5 == 0)
}
});Set property value on condition
Mit dem Schlüsselwort setifelse kann ein Eigenschaftswert abhängig von der Erfüllung einer Bedingung gesetzt werden. setifelse erwartet als Wert ein Objekt mit den folgenden vier Eigenschaften:
- name ist der Eigenschaftsname
- value_true ist der Wert, welcher der Eigenschaft zugewiesen wird, wenn die Bedingung zutrifft
- value_false ist der Wert, welcher der Eigenschaft zugewiesen wird, wenn die Bedingung nicht zutrifft
- condition (alternativ: cond) ist die Bedingung, die erfüllt sein muß
setifelse erzeugt immer die in name definierte Eigenschaft; nur der Wert wird bedingungsabhängig gesetzt. Um die Möglichkeit zu haben, dies gegebenenfalls zu verhindern, kann der Wert von condition auf NULL gesetzt werden
Das Schlüsselwort setifelse ist multi-fähig
// value of the input element will be set to "a hand" if str === 'hand' otherwise to "a foot"
{
element: 'input',
type: 'text',
id: 'foo',
setifelse: {
name: 'value',
value_true: 'a hand',
value_false: 'a foot',
condition: (str === 'hand')
}
}
// special case condition === null
// the property will be set only, if x < 0 or x > 0
// if x == 0, the property will not be created.
var mycond,
x = randomInteger(-1, 1); // custom function
if (x < 0) {
mycond = false; // creates property className="negative"
} else if (x > 0) {
mycond = true; // creates property className="positive"
} else {
mycond = null; // creates no className property
}
var div_bar = K345.dElement({
element: 'div',
id: 'bar',
setif: {
name: 'className',
value_true: 'positive',
value_false: 'negative',
condition: mycond
}
});Init function after creation of the element tree
Mit dem Schlüsselwort init kann eine Referenz auf eine Funktion übergeben werden, welche nach der Erzeugung der Element-Struktur ausgeführt wird. Dadurch kann z.B. ein ausserhalb der erzeugten Struktur liegendes Element abhängig vom Wert eines erzeugten Elements manipuliert werden.
Innerhalb der Funktion steht das aktuelle Element als »this« sowie der gesamte erzeugte Element-Baum als Parameter der Funktion zur Verfügung.
Wichtig: Die init-Funktion wird aufgerufen, sobald dElement den Element-Baum erzeugt hat, d.h. bevor die Elemente ins Dokument integriert werden. Zugriff ist zu diesem Zeitpunkt daher nur per elrefs oder collect möglich.
// init example
K345.dAppend(document.body, {
element: 'select',
name: 'foo',
child: [{
element: 'option',
value: 'hello',
text: 'first'
}, {
element: 'option',
value: 'world',
text: 'second',
selected: true
}],
init: function () {
// update an existing element elsewhere.
// 'this' references the 'select' element
document.getElementById('some_element').textContent = this.value;
}
});Element duplication with loops (Loops)
dElement bietet eine rudimentäre Unterstützung von Schleifen an, mit denen man gleichartige Elemente / Elementstrukturen leichter erzeugen kann. Dabei können in Zeichenketten-Deklarationswerten Platzhalter gesetzt werden, welche durch den aktuellen Zählwert ersetzt werden.
- Wird als Schlüsselwort loop verwendet, so werden nur die Platzhalter in der aktuellen Ebene sowie einer eventuellen direkten child Unterdeklaration ersetzt.[6]
- Wird als Schlüsselwort loopdeep verwendet, so werden die Platzhalter in der aktuellen Ebene sowie allen Unter-Deklaration ersetzt.[6]
- Wird als Schlüsselwort loopstop verwendet, so wird die Ersetzung der Platzhalter ab dieser Ebene beendet. (Wert beliebig)
loop bzw. loopdeep erwarten als Wert entweder eine Ganzzahl ≥0 oder ein Objekt, das mindestens die Eigenschaft count enthält, deren Wert eine Ganzzahl ≥0 ist. Über die optionalen Eigenschaften start und step kann die Schleifenvariable beeinflusst werden. Dazu später mehr.
Ein Platzhalter ist in der einfachsten Form die Zeichenfolge !!n!! bzw. !!c!!. Dieser wird durch den aktuellen Wert der Schleifenvariable ersetzt [Standardmäßig startend bei 0].
Der Unterschied zwischen !!n!! und !!c!! ist, daß der Wert von !!n!! über die Objekt-Eigenschaften start und step angepasst werden kann, während sich der Wert von !!c!! grundsätzlich auf den aktuellen Schleifen-Zähler bezieht. Die weiter unten beschriebenen mathematischen Berechnungen können auf beide Platzhalter angewendet werden.
Es ist zur Zeit nicht möglich, komplexere Ausdrücke mit mehreren n und / oder c innerhalb einer Platzhalter-Einheit (z.B. !!2n-3c!!) zu erzeugen.
Der Platzhalter !!v!! dient dazu, Werte aus einem übergebenen Array einzufügen. Dabei wird im ersten Schleifendurchlauf das erste Array-Element, im zweiten Durchlauf das zweite Element usw. verwendet. Das Array wird mittels der Eigenschaft values übergeben.
Ist die Eigenschaft valuesrepeat gesetzt (Wert beliebig), wird beim Erreichen des letzten Array-Elements wieder mit dem Ersten fortgefahren, falls count größer ist als die Länge des Arrays in values.
Wenn values ein Array ist, wird die Eigenschaft count automatisch auf die Array-Länge gesetzt, falls die Eigenschaft count nicht gesetzt wurde.
Platzhalter dürfen innerhalb der begrenzenden !! Paare keine Leerzeichen, Tabulatoren oder sonstige Abstandzeichen oder weitere Buchstaben oder nicht hier dokumentierte Symbole enthalten!
Das Beziehen von Element-Referenzen mit collect oder elrefs funktioniert in einer loop-Schleife zur Zeit nicht. Es wird empfohlen, die entsprechenden Element-Definitionen in einer Schleife; z.B. mit for oder while; zu erzeugen und dort mit collect oder elrefs die jeweilige Elementreferenz zu sichern.
// example 1
var el = K345.dElement({
element: 'span',
loop: {count: 5}, // or 'loop: 5'
text: ' !!n!! ' // simple placeholder
});
// examples 2 + 3
var el = K345.dElement({
element: 'p',
loop: {count: 3}, // example 3: replace 'loop' keyword with 'loopdeep'
id: 'p-!!n!!',
child: { // first level descendant; will be replaced
element: 'span',
id: 'span-!!n!!'
child: { // second level descendant; will not be replaced with keyword 'loop'
element: 'span',
id: 'subspan-!!n!!',
text: 'text !!n!!'
}
}
});<!-- example 1 -->
<span> 0 </span><span> 1 </span><span> 2 </span><span> 3 </span><span> 4 </span>
<!-- example 2: loop keyword
only ph in current declaration and first level descendants are replaced -->
<p id="p-0"><span id="span-0"><span id="subspan-!!n!!">text !!n!!</span></span></p>
<p id="p-1"><span id="span-1"><span id="subspan-!!n!!">text !!n!!</span></span></p>
<p id="p-2"><span id="span-2"><span id="subspan-!!n!!">text !!n!!</span></span></p>
<!-- example 3: loopdeep keyword
ph in current declaration and all descendants are replaced -->
<p id="p-0"><span id="span-0"><span id="subspan-0">text 0</span></span></p>
<p id="p-1"><span id="span-1"><span id="subspan-1">text 1</span></span></p>
<p id="p-2"><span id="span-2"><span id="subspan-2">text 2</span></span></p>dElement unterstützt weitere Platzhalter-Optionen, die den Wert beeinflussen können. Möglich sind Addition, Subtraktion und Potenzieren[7]. Dazu wird hinter dem »n« (»c«) im Platzhalter eines der Symbole + - ^ sowie die zu addierende bzw. subtrahierende Zahl eingefügt. So wird z.B. durch den Platzhalter !!n+3!! eine Zahlenfolge 3,4,5,6,7… erzeugt, bei !!n-2!! die Zahlenfolge -2,-1,0,1,2… und bei !!n^2!! würde die Zahlenfolge 0,1,4,9,16… erzeugt.
Steht vor dem »n« (»c«) eine Zahl, wird der Schleifenwert mit dieser Zahl multipliziert. Dadurch können Platzhalter wie !!3n+2!! erzeugt werden (hier wird erst der Zählwert mit 3 multipliziert und dann 2 addiert, was eine Zahlenfolge von 2, 5, 8, 11, 14… ergibt[8]). Es kann optional nach der Zahl ein Multiplikationssymbol (* oder •) eingefügt werden, z.B. !!4•n!!.
Eine weitere Möglichkeit, die Zahlenfolge zu verändern, ist die Verwendung der Eigenschaften start und step im Objektwert von loop bzw. loopdeep.
- start beeinflusst den initialen Wert der Schleifenvariable, d.h. die Schleife startet bei diesem Wert statt bei 0.
- step beeinflusst die Schrittweite pro Schleifendurchlauf. Die Schleifenvariable wird jeweils um diesen Wert erhöht. Dieser Wert darf nicht 0 sein
// example 1
var el = K345.dElement({
element: 'div',
child: {
element: 'span',
loop: {count: 3, start: 1}, // values of loop var are 1..3
title: 'counter: !!n!!', // replace by loop var values => 1,2,3
id: 'span-!!3n+2!!', // multipy loop var value with 3 and add 2 => 5,8,11
text: 'text !!2*n!!' // each loop var value is muliplied with 2 => 2,4,6
}
});
// example 2
var el = K345.dElement({
element: 'div',
child: {
element: 'span',
loop: {count: 3, start: 2, step: 4}, // values of loop var are 2,6,10
id: 'span-!!n-1!!', // 1 is subtracted from each loop var value => 1,5,9
text: 'text !!n+2!!', // 2 is added to each loop var value => 4,8,12
title: 'title !!2*c!!' // c is always loop counter (0,1,2) => 0,2,4
}
});
// example 3
var el = K345.dElement({
element: 'div',
child: {
element: 'span',
text: '!!n+1!!. This fruit is !!v!!.',
loop: {
count: 3,
values: ['an apple', 'a pear', 'a mango']
}
}
});
// example 4
var el = K345.dElement({
element: 'span',
text: '!!v!! ',
loop: {
count: 16,
values: ['A', 'B', 'C'],
valuesrepeat: 1
}
});<!-- example 1 -->
<div>
<span id="span-5" title="counter: 1">text 2</span>
<span id="span-8" title="counter: 2">text 4</span>
<span id="span-11" title="counter: 3">text 6</span>
</div>
<!-- example 2 -->
<div>
<span id="span-1" title="title 0">text 4</span>
<span id="span-5" title="title 2">text 8</span>
<span id="span-9" title="title 4">text 12</span>
</div>
<!-- example 3 -->
<div>
<span>1. This fruit is an apple.</span>
<span>2. This fruit is a pear.</span>
<span>3. This fruit is a mango.</span>
</div>
<!-- example 4 -->
<span>A B C A B C A B C A B C A B C A </span>[6] Wird in einer Unter-Deklaration eines der Schlüsselwörter loop, loopstop oder loopdeep verwendet, wird die aktuelle Ersetzung beendet.
[7] Zumindest vorerst wird auf die Erkennung und Verarbeitung komplexerer Operationen zugunsten von Geschwindigkeit und Speicherverbrauch verzichtet; falls es erforderlich ist, kann — wie es zur Vervielfältigung bisher ohnehin notwendig war — mit einer externen Scheife und einem Array gearbeitet werden.
[8] für !!3n+2!!: 3•0 + 2 = 2; 3•1 + 2 = 5; 3•2 + 2 = 8; 3•3 + 2 = 11; 3•4 + 2 = 14 …
Events
dElement bietet verschiedene Möglichkeiten, einem Element einen Eventhandler zuzuweisen:
- Event-Methode eines Elements
- z.B. element.addEventListener()
- externe Event-Verwaltung
- z.B. Cross-Browser-Methoden
- Eigenschaft / Attribut
- z.B. onclick, onchange usw.
Native event method of element
Zuweisung über das Schlüsselwort event. Als Wert wird ein Objekt mit den Eigenschaften function und arguments (alternativ: func bzw. args) erwartet.
Der Eigenschaft func muß eine Zeichenkette mit dem Namen der Event-Methode des Elements sein. (z.B. 'addEventListener' oder 'attachEvent'). Wird die Eigenschaft nicht definiert, wird 'addEventListener' als Standard verwendet.
Die Eigenschaft args ist ein Array mit den einzelnen Parametern, die der externen Funktion übergeben werden. Die Reihenfolge im Array muß der Reihenfolge der erwarteten Funktions-Parameter entsprechen.
// example: element.addEventListener(type, function, capture)
var btn2 = K345.dElement({
element: 'button',
text: 'Button 1',
event: {
func: 'addEventListener', // MUST be string here
args: [
'click',
function () {
alert('button has been clicked');
},
false
]
}
});Custom event method
Zuweisung über das Schlüsselwort event. Als Wert wird ein Objekt mit den Eigenschaften func und args erwartet.
Der Eigenschaft func muß eine Referenz auf die externe Event-Funktion zugewiesen werden.
Die Eigenschaft args ist ein Array mit den einzelnen Parametern, die der externen Funktion übergeben werden. Die Reihenfolge im Array muß der Reihenfolge der erwarteten Funktions-Parameter entsprechen.
Da zum Zeitpunkt der Deklaration im Objekt-Literal üblicherweise noch keine Referenz auf das Ziel-Element existiert, dem der Handler zugewiesen werden soll, ist der Platzhalter #el# an der entsprechenden Stelle einzusetzen. Dieser Platzhalter kann entfallen, wenn die externe Funktion die Elementreferenz als ersten Parameter erwartet.
Als weitere möglicher Platzhalter sind #elp# und #elpp# definiert. Hiermit wird das Event statt an das für #el# geltende (aktuell definierte) Element an dessen Eltern- bzw. Großeltern-Element gelegt.
// example: external function "addEvent(element, type, function)"
var btn = K345.dElement({
element: 'button',
text: 'Button 2',
event: {
func: addEvent, // MUST be function reference here
// placeholder '#el#' may be omitted here (optional), because addEvent()
// expects the element reference to be the first parameter. dElement handles this.
args: ['#el#', 'click', function () {
alert('button has been clicked');
}]
}
});
// example: external function "myEvent(type, element, function)"
var btn3 = K345.dElement({
element: 'button',
text: 'Button 3',
event: {
func: myEvent, // MUST be function reference here
// placeholder '#el#' is REQUIRED here, because the element is not the first
// parameter of myEvent()
args: ['click', '#el#', function () {
alert('button has been clicked');
}]
}
});on… property / attribute
Klassische Event-Zuweisung; entspricht element.on<event_type> = <function reference>
// example: direct event assignment using onclick property
var btn4 = K345.dElement({
element: 'button',
text: 'Button 4',
onclick: function () {
alert('button has been clicked');
}
});event ist ein multi-fähiges Schlüsselwort
Element references
elrefs
Enthält die Baum-Deklaration das Schlüsselwort elrefs und dessen Wert ist ein Objekt, so werden sämtliche erzeugten Elemente mit id oder name-Eigenschaft in diesem Objekt abgelegt. Es werden dazu zwei Unterobjekte i (für IDs) und n (für Namen) angelegt, die jeweils die Elemente aufnehmen. Da mehrere Elemente denselben Namen nutzen können, ist der Datentyp von obj.n[name] immer ein Array.
Momentan nicht in Loops verwenden
// define object for reference storage
// child objects named „i“ (elements with property „id“) and „n“ (elements
// with property „name“) will be created if they don't already exist.
var myrefs = {};
var whatever = K345.dElement({
element: "div",
id: "aDiv",
elrefs: myrefs, // storage object is passed to dElement() here
child: {
element: "input",
type: "text",
id: "anInput",
name: "roger_stan_klaus"
}
});
// myrefs.i.aDiv now holds the reference to the div element which is identical to
// document.getElementById("aDiv")
alert(myrefs.i.aDiv); // [object HTMLDivElement]
// since the input element has both an id and a name property, it's reference has
// been stored in both "myrefs.i.anInput" and "myrefs.n.roger_stan_klaus[0]"
alert(myrefs.i.anInput); // [object HTMLInputElement]
// there may be multiple elements sharing the same name, that's why an index is
// required
alert(myrefs.n.roger_stan_klaus[0]); // [object HTMLInputElement]Das Referenzobjekt wird für alle Aufrufe von dElement verwendet, bis ein anderes Objekt oder null zugewiesen wird.
Die Deklaration wird von Oben nach Unten verarbeitet, d.h. wenn beispielsweise elrefs: null in einem Unterzweig gesetzt wird, werden auch Elemente in höheren Ebenen, die später deklariert werden, nicht mehr erfasst. Durch erneutes Setzen von elrefs mit der Objektreferenz ab dem wieder zu erfassenden Element werden die Referenzen wieder gesammelt.
var myrefs = {};
K345.dElement([{
element: 'div', id: 'd01', text: 'div1',
elrefs: myrefs // save references
}, {
element: 'div', id: 'd02', text: 'div2',
child: { element: 'span', text: 'span1', id: 's01' }
}, {
element: 'div', id: 'd03', text: 'div3',
elrefs: null, // stop saving
child: { element: 'span', text: 'span2', id: 's02' }
}, {
element: 'div', id: 'd04', text: 'div4',
}, {
element: 'div', id: 'd05', text: 'div5',
elrefs: myrefs, // save again
child: { element: 'span', text: 'span3', id: 's03' }
}, {
element: 'div', id: 'd06', text: 'div6',
}]);
// myrefs.i contains references to d01, d02, s01, d05, s03 and d06
// remember: declaration is processed top-downcollect
Um in einer Baum-Deklaration die Referenzen auf bestimmte Elemente zu erhalten, kann für jedes Element mit dem Schlüsselwort collect die Referenz auf ein Array übergeben werden. Jedes erzeugte Element mit diesem Schlüsselwort wird dann in der Reihenfolge der Erzeugung in diesem Array abgelegt.
Alternativ ist es möglich, ein Objekt mit den Eigenschaften obj mit einer Objekt-Referenz und name mit einem eindeutigen Namen zu übergeben. Die Referenz auf das erzeugte Element wird dann in obj[name] gespeichert.
Verschiedenen Elementen können jeweils separate Arrays bzw. Objekte zugewiesen werden.
Momentan nicht in Loops verwenden
/* ------ Example #1 ------- */
var my_el = [], // collects element references
your_el = [], // collects element references
els;
els = K345.dElement({
element: "ul",
collect: your_el, // push ul reference to your_el
child: [{
element: "li",
collect: your_el, // push ul reference to your_el
child: {
element: "button",
name: "bar",
collect: my_el, // push ul reference to my_el
text: "click me"
}
}, {
element: "li",
collect: your_el, // push li reference to your_el
child: {
element: "button",
name: "bar",
collect: my_el, // push ul reference to my_el
text: "don't click me"
}
}]
});
// my_el holds references to the first „li“ and two „button“ elements
// your_el holds references to „ul“ and the second „li“/* ------ example 2 ------- */
var hg = {},
els;
els = K345.dElement({
element: 'div',
collect: {obj: hg, name: 'mydiv-1'},
child: {
element: 'span',
collect: {obj: hg, name: 'myspan-1'}
}
});
// the passed object 'hg' now has been filled like this (if written in object literal
// notation):
{
'mydiv-1': reference_to_div_element,
'myspan-1': reference_to_span_element
}/* ------ example #3 ------- */
var inp = [], // collect references of declarations containing "collect: inp"
dv = [],
els = [],
i;
// create 10 div elements with child nodes
// <div id="mydiv0"><p>Lorem ipsum...</p><input name="myinput" id="myinp_0"></div>
// ...
// <div id="mydiv9"><p>Lorem ipsum...</p><input name="myinput" id="myinp_9"></div>
for (i = 0; i < 10; i++) {
els[i] = {
element: "div",
id: "mydiv" + i,
collect: dv // collect all div element references to array "dv"
child: [{
element: "p",
text: "lorem ipsum dolor..."
}, {
element: "input",
name: "myinput",
id: "myinp_" + i,
collect: inp // collect all input element references to array "inp"
}]
};
document.body.appendChild(K345.dElement(els));
}
alert(inp.length); // 10
alert(inp[5].id); // myinp_6/* ------ example #4 ------- */
var hg = {}, moo = [], els;
// note that trailing parts of collect keyword names don't affect collections
els = K345.dElement({
element: 'div',
id: 'mydiv-1',
collect_1: {obj: hg, name: 'd1'},
collect_2: moo,
child: [{
element: 'span',
id: 'myspan-1',
collect_me: {obj: hg, name: 's1'}
}, {
element: 'span',
id: 'myspan-2',
collect: {obj: hg, name: 's2'},
collect_2: moo
}, {
element: 'span',
id: 'myspan-3',
collect_moo: moo
}]
});
// the array 'moo' now contains references to elements with these IDs:
// moo = [ div#mydiv-1, span#myspan-2, span#myspan-3 ]
//
// the object 'hg' contains references to elements with these IDs:
// hg = { d1: div#mydiv-1, s1: span#myspan-1, s2: span#myspan-2 }wrapper function dAppend
Node|Fragment reference = K345.dAppend(
String|Node target id|reference|query,
Object declaration
[, Const mode_flag = K345.DAPPEND_APPEND ]
);
Um den Elementbaum zu erstellen und direkt an ein bestehendes Element anzuhängen, gibt es die kleine Helferfunktion dAppend, die alle Schritte ausführt.
Als ersten Parameter erwartet sie eine Referenz auf das Element [oder eine Zeichenkette, die entweder einer Element-ID entspricht oder einem gültigen query für querySelector()], an welches der erzeugte Elementbaum angehängt werden soll; als zweiten Parameter eine beschreibende Objekt-Deklaration wie bei dElement. Der optionale dritte Parameter legt den Modus fest, mit dem der erzeugte Elementbaum relativ zum übergebenen Element ins DOM eingehängt wird.
Zur weiteren Verarbeitung gibt die Funktion zusätzlich den erzeugten Element-Baum zurück (oder null, falls ein Fehler aufgetreten sein sollte)
var tree = K345.dAppend("my_element", el_def);Der Code aus dem Beispiel könnte mit dAppend folgendermaßen vereinfacht werden:
// same as above, now using K345.dAppend() instead of K345.dElement()
var my_link = K345.dAppend("my_element", {
element: "a", href: link_href, title: "link to new page", child: {
element: "span", child: [
{element: "img", src: img_src, width: img_w, alt: "an arrow"},
{text: "some text"}
]
}
});effects of each mode flag
dAppend bietet über Modus-Flags die Möglichkeit, erzeugte Elemente auf verschiedene Weise relativ zum Referenz-Element in den vorhandenen Element-Baum einzuhängen. Dies kann entweder durch eine der vordefinierten Konstanten geschehen oder durch eine Zeichenkette ähnlich wie bei z.B. insertAdjacentElement.
Mode flags may not be combined
<p id="my_p">hello</p>// K345.dAppend() call for all examples:
K345.dAppend(
'my_p',
{element: 'span', text: ' world '} // creates <span> world </span>
<APPEND_MODE>
);<p id="my_p">hello<span> world </span></p><p id="my_p"><span> world </span>hello</p><p id="my_p">hello</p><span> world </span><span> world </span><p id="my_p">hello</p><p id="my_p"><span> world </span></p><span> world </span>Source code
- dElement.js last changed: 2026-04-27
- Source-Code ansehen
Downloads
- dElement.js [enthält dElement(), dAppend() und attrNames] (minified)
- dElement.js [enthält dElement(), dAppend() und attrNames]
Github
Compatibility
Benötigt einen kompatiblen Browser. (so ziemlich alle ab 2012 / 2013)
Sollte dElement in einem Browser nicht ordnungsgemäß funktionieren, bitte ich um genau Beschreibung des Fehlers.
Mapping of attribute names
In Javascript müssen diverse Eigenschaftsnamen eine ganz bestimmte Schreibweise haben. So kann beispielsweise elem.for = "foo"; zum Setzen der Eigenschaft for bei Label-Elementen nicht verwendet werden, da for innerhalb von Javascript bereits eine Bedeutung als Schüsselwort hat. Hier wäre elem.htmlFor = "foo"; zu verwenden.
dElement wandelt die Eigenschaftsnamen anhand dieser Tabelle entsprechend um, sodaß man beim Erstellen einer Baum-Deklaration die üblichen Attributnamen wie auch in HTML verwenden kann. (Bei Javascript-eigenen Schlüsselwörtern wie z.B. for müssen die Attributnamen als Zeichenkette notiert werden -> "for":
<label for="myInput">…</label> // first option: use the HTML attribute name as quoted property
{ element: 'label', 'for': 'myInput', child: { … } }
// second option: use the specific property name
{ element: 'label', htmlFor: 'myInput', child: { … } }/** conversion table for HTML-attribute names
@type {object} */
K345.attributeNames = K345.attributeNames || {
acceptcharset: 'acceptCharset', accesskey: 'accessKey', alink: 'aLink',
bgcolor: 'bgColor', cellindex: 'cellIndex', cellpadding: 'cellPadding',
cellspacing: 'cellSpacing', charoff: 'chOff', 'class': 'className',
codebase: 'codeBase', codetype: 'codeType', colspan: 'colSpan',
datetime: 'dateTime', 'for': 'htmlFor', frameborder: 'frameBorder',
framespacing: 'frameSpacing', ismap: 'isMap', longdesc: 'longDesc',
marginheight: 'marginHeight', marginwidth: 'marginWidth', maxlength: 'maxLength',
nohref: 'noHref', noresize: 'noResize', nowrap: 'noWrap',
readonly: 'readOnly', rowindex: 'rowIndex', rowspan: 'rowSpan',
tabindex: 'tabIndex', usemap: 'useMap', valign: 'vAlign', vlink: 'vLink'};Diese Umwandlungstabelle ist in der Download-Datei delement.js enthalten.