Hopp til innholdet

cjohansen.no

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:

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:

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.

Muligens relatert

2006 - 2010 Christian Johansen Creative Commons Lisens