A proposal for Element constructors

Hi folks,

* Background *

I have been working on making DOM objects look and feel more like ordinary
JavaScript objects. I implemented Event constructors  [1], for example.


* Proposal *

Element.create() has proposed and been under discussion [2]. Besides
Element.create(), I propose constructors for Elements, like "new
HTMLButtonElement(...)", "new HTMLDivElement(...)" and "new
HTMLVideoElement(...)". I think that both Element.create() *and* Element
constructors are useful.

For example,

    var button = new HTMLButtonElement( {disabled: true},
                                                                        [new
TextNode("Click Me!!"),
                                                                         new
Image( {src: "http://example.com/xxx.png <http://xxx.com/xxx.png>"} ) ] );
    document.body.appendChild(button);

is equivalent to the following HTML:

    <button disabled>
    Click Me!!
    <img src = "http://example.com/xxx.png <http://xxx.com/xxx.png>" />
    </button>

As shown in above, the constructor has two arguments. The first one is a
dictionary of Element properties. The second one is an array of Nodes, which
become child nodes of the Element.


* Advantages of Element constructors *

(a) Better errors when you misspell it
Element.create("vdieo", {src: ...} ) ==> No error; HTMLUnknownElement is
created
new HTMLVdieoElement( {src: ...} ) ==> ReferenceError; Stack trace points
you to the faulty line of code

(b) Consistency with other constructable DOM objects
e.g. new XMLHttpRequest(), new Image(), new Event(), new CustomEvent(), new
MessageEvent(), ...

(c) Enables to subtype DOM objects in the future
We are planning to make DOM objects subtype-able, like this:

    function MyButton(text) {
        HTMLButtonElement.call(this);    /* (#) */
        this.textContent = text;
    }
    MyButton.prototype = Object.create(HTMLButtonElement.prototype, {...});
    var fancyButton = new MyButton("Click Me!!");

In order to make the line (#) work, HTMLButtonElement must have a
constructor.


* Spec examples *

interface [
    NamedConstructor(optional HTMLButtonElementInit initDict, optional
NodeArray children)
] HTMLButtonElement : HTMLElement {
    attribute boolean disabled;
    attribute DOMString value;
    ... (omitted)
}

dictionary HTMLButtonElementInit : HTMLElementInit {
    boolean disabled;
    DOMString value;
    ... (omitted)
}

interface [
    NamedConstructor(optional HTMLElementInit initDict, optional NodeArray
children)
] HTMLElement : Element {
    attribute DOMString lang;
    attribute DOMString className;
    ... (omitted)
}

dictionary HTMLElementInit : ElementInit {
    DOMString lang;
    DOMString className;
    ... (omitted)
}

interface Element : Node {
    readonly attribute DOMString tagName;
    ... (omitted)
}

dictionary ElementInit : NodeInit {
    DOMString tagName;
    ... (omitted)
}

interface Node {
    readonly attribute unsigned short nodeType;
    ... (omitted)
}

dictionary NodeInit {
    unsigned short nodeType;
    ... (omitted)
}


* Discussion

(a) Element.create() *and* Element constructors?
I think that both are useful for the reasons that dominicc pointed out in
[3]. Element.create() is good when we have a tag name in hand, like

    var tag = "button";
    Element.create(tag, { }, ...);

On the other hand, Element constructors have the advantages that I described
above.

(b) How to specify properties and attributes in the dictionary argument
A property and an attribute are two different things [4]. A property is the
thing that can be set by a setter like foo.value, and an attribute is the
thing that can be set by foo.setAttribute(). A discussion has been conducted
on how we can set up properties and attributes in the dictionary argument
[2]. Proposed approaches in [2] are as follows:

(b-1) Let a constructor have two dictionaries, one for properties and the
other for attributes, e.g. new HTMLButtonElement( {disabled: true}, {class:
"myButton", onclick: func}, ...)
(b-2) Add a prefix character to attributes, e.g new HTMLButtonElement(
{disabled: true, "@class": "myButton", "@onclick": func} )
(b-3) Introduce a reserved key "attributes", e.g new HTMLButtonElement(
{disabled: true, attributes: {class: "myButton", onclick: func} } )

Another difficulty around attributes is how to naturally set a value of a
boolean attribute in the dictionary when we have the value in hand. In case
of a property, you just need to write like this:

    var b = new HTMLButtonElement({disabled: value});

However, in case of an attribute, we need to write like this:

    var options = {};
    if (value)
        options.disabled = "";
    var b = new HTMLButtonElement(options);

Basically, I think that we should keep the succinctness of constructors and
should not introduce a new syntax just for allowing people to set attributes
in the constructor. Thus, I propose that we allow only properties in the
dictionary, at least at the present moment. If we found in the future that
people wish to set attributes in the constructor, we can discuss the feature
again then.

(c) How to specify child Elements in the constructor argument
Possible approaches are as follows:

(c-1) As variable arguments, e.g. new HTMLButtonElement( { ... }, child1,
child2, child3, ...)
(c-2) As an array, e.g. new HTMLButtonElement( { ... }, [child1, child2,
child3] )
(c-3) As variable arguments. We expand arguments of type array. e.g. new
HTMLButtonElement( { ... }, child1, [child2, child3], child4, ...) is
equivalent to new HTMLButtonElement({ ... }, child1, child2, child3, child4,
...)

I guess that (c-3) is not good for the complicated rule. I think that both
(c-1) and (c-2) are possible, but I prefer (c-1). I think that implicit
array expansion is not necessary, since ES6 is introducing "..." operator
for array expansion [6]. In other words, you can write like this:

    var array = [child1, child2, child3, ......];
    new HTMLButtonElement({ ...... }, ...array);

(d) What should the owner document of the created Element be?
I think that we can assume that the owner document is window.document, as
existing constructors (new Image(), new Audio() and new Option()) is
assuming. Some cases can expect an owner document other than
window.document, but they are not so common. I believe that we should focus
on making most of practical use cases simpler rather than supporting
specialized cases that do not really need a convenience shortcut.

(e) How to specify a namespace of attributes
I propose that we do not support namespaced attributes like "xmlns:foo",
since they are rare [5]. Again, we should focus on making most of practical
use cases simpler.

(f) How to create an unknown element that can be created by
document.createElement("unknooooown")
Fortunately, the HTML5 spec has HTMLUnknownElement. You can create an
unknown element by new HTMLUnknownElement( {tagName: "unknooooown"}, ...)

(g) How to handle HTMLElements that have more than one tags
For example, both <ins> and <del> are tags of HTMLModificationElement. Then,
how does the constructor look like? I think that we can easily handle this
issue by distinguishing tags using a tagName property, e.g. new
HTMLModificationElement( {tagName: "ins"}, ...) or new
HTMLModificationElement( {tagName: "del"}, ...)


[1] https://bugs.webkit.org/show_bug.cgi?id=67824
[2] http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0537.html
[3] http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0707.html
[4] http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0666.html
[5] http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0721.html
[6] http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts (11.2.4
Argument Lists)



-- 
Kentaro Hara, Tokyo, Japan (http://haraken.info)

Received on Wednesday, 26 October 2011 03:43:47 UTC