En nyttigere document.createElement
Å opprette nye elementer med nettlesernes DOM-APIer er tungvindt og "verbost". Idag bygger vi en hjelpemetode som lager nye elementer på en langt mer kompakt måte.
Nok en tutorial idag altså. Målene er de samme som sist:
- Først og fremst: Lære. Se et litt mer komplekst eksempel
- Øve på testdrevet utvikling
- Å lage et verktøy som kan hjelpe til i små prosjekter der rammeverk på 100k blir overkill
Som en ekstra bonus kommer vi til sist til å bygge dagens kode sammen med $-funksjonen fra sist - en illustrasjon i hvordan koden fra sist er fleksibel nok til å la seg enkelt utvide.
Utgangspunktet
Tanken er å lage en metode som kan lage et element, og i samme operasjon sette vilkårlig mange attributter og innholdet. Noen enhetstester sier mer enn, vel, mange ord.
var createElementTestCase = new YAHOO.tool.TestCase({
name: "Create DOM element utility",
testCreateWithTagName: function() {
var assert = YAHOO.util.Assert;
var element = createElement("a");
assert.isInstanceOf(HTMLElement, element);
assert.areEqual(1, element.nodeType);
assert.areEqual("a", element.tagName.toLowerCase());
element = createElement("div");
assert.isInstanceOf(HTMLElement, element);
assert.areEqual(1, element.nodeType);
assert.areEqual("div", element.tagName.toLowerCase());
},
testCreateWithAttributes: function() {
var assert = YAHOO.util.Assert;
var element = createElement("a", {
href: "http://www.cjohansen.no/",
className: "external"
});
assert.isInstanceOf(HTMLElement, element);
assert.areEqual("http://www.cjohansen.no/", element.href);
assert.areEqual("external", element.className);
},
testCreateWithContent: function() {
var assert = YAHOO.util.Assert;
var element = createElement("a", {}, "Link text");
assert.isInstanceOf(HTMLElement, element);
assert.areEqual("Link text", element.innerHTML);
},
testCreateWithParent: function() {
var assert = YAHOO.util.Assert;
var element = createElement("a", {}, null, document.body);
var links = document.body.getElementsByTagName("a");
assert.isInstanceOf(HTMLElement, element);
assert.areEqual(1, links.length);
assert.areEqual(element, links[0]);
},
testCreateFull: function() {
var assert = YAHOO.util.Assert;
var element = createElement("a", {
href: "http://www.cjohansen.no/",
id: "cjlink"
}, "cjohansen.no", document.body);
var link = document.getElementById("cjlink");
assert.isInstanceOf(HTMLElement, element);
assert.areEqual(element, link);
assert.areEqual("http://www.cjohansen.no/", element.href);
}
});
Testene feiler, og utgjør et fint mål. Metoden skal kunne lage et element med følgende data:
-
tagName- elementtypen som skal lages -
attributes- en objektliteral med attributtnavn som egenskapnavn og attributtverdier som verdier -
content- tekst eller HTML som skal settes inn i det nye elementet -
parent- foreldreelementet, hvis noe
Kun tagnavnet er påkrevd. Implementasjonen er relativt rett frem:
function createElement(tagName, attributes, content, parent) {
var element = document.createElement(tagName);
attributes = attributes || {};
// Set attributes
for (var attr in attributes) if (attributes.hasOwnProperty(attr)) {
element[attr] = attributes[attr];
}
// Add content, if provided
if (content) {
element.innerHTML = content;
}
// Add to parent, if provided
if (parent) {
parent.appendChild(element);
}
return element;
}
Heldig for oss så var vi smarte nok i første omgang til at testene nå kjører.
Integrere i cj.Element
Forleden dag laget vi jo et navnerom for element-utvidelser, og denne metoden kunne passe godt inn der. For eksempel kunne det være nyttig å gjøre:
var div = $("testDiv");
div.create("a", { href: "http://www.cjohansen.no/" }, "cjohansen.no");
// Resultat:
// <div id="testDiv">
// <!-- HTML fra tidligere -->
// <a href="http://www.cjohansen.no/">cjohansen.no</a>
// </div>
Eller formulert som en ny test:
testCreateFromElement: function() {
var assert = YAHOO.util.Assert;
var body = $(document.body);
var element = body.create("div", { id: "testDiv" });
assert.isInstanceOf(HTMLElement, element);
assert.isNotUndefined(element.hasClassName);
assert.areEqual($("testDiv"), element);
assert.areEqual(document.body, element.parentNode);
}
Problemet med den nye metoden vår er bare at den ikke tar et element som første parameter - parent-parameteret er det siste metoden tar. Dette gir meningen når metoden kalles som tidligere fordi parent ofte er null. Men, vi kan trikse litt. Når metoden kjøres gjennom et annet element ( element.create(/* ... */); så kan vi ta første parameter som foreldreelement, mens vi ellers behandler parameterne som tidligere. Det høres kanskje vanskeligere ut enn det er:
cj.Element = {
/* ... */
create: function() {
var tagName, attributes, content, parent;
// If first argument is an HTMLElement, we're running from within
// an element
if (arguments[0] instanceof HTMLElement) {
parent = arguments[0];
tagName = arguments[1] || parent.tagName;
attributes = arguments[2] || {};
content = arguments[3] || null;
} else {
tagName = arguments[0];
attributes = arguments[1] || {};
content = arguments[2] || null;
parent = arguments[3] || null;
}
var element = document.createElement(tagName);
attributes = attributes || {};
// Set attributes
for (var attr in attributes) if (attributes.hasOwnProperty(attr)) {
element[attr] = attributes[attr];
}
// Add content, if provided
if (content) {
element.innerHTML = content;
}
// Add to parent, if provided
if (parent) {
parent.appendChild(element);
}
// If called from an element, ie element.create(/* ... */)
// extend new element as well
if (arguments[0] instanceof HTMLElement) {
cj.Element.extend(element);
}
return element;
}
};
Metoden er ellers som den var, og testene kjører fortsatt. Merk at jeg i dette siste eksempelet har inkludert all kode og tester fra sist også.
Og dermed har vi sett et eksempel på hvordan element-verktøyet vårt fra sist kan videreutvikles med mer funksjonalitet. Det er slike ting som ligger i bunnen av rammeverkene vi bruker, og det er morsomt å jobbe seg gjennom noen praktiske eksempler for å bedre forstå de underliggende problemene som vi er vant til å få abstrahert bort gjennom rammeverkene.
Vel møtt i morgen for en prat om mixins og multippel arv i JavaScript.