W3C home > Mailing lists > Public > public-webapps@w3.org > April to June 2013

Re: [webcomponents]: Of weird script elements and Benadryl

From: Allen Wirfs-Brock <allen@wirfs-brock.com>
Date: Fri, 12 Apr 2013 17:41:49 -0700
Cc: Rick Waldron <waldron.rick@gmail.com>, Daniel Buchner <daniel@mozilla.com>, John J Barton <johnjbarton@johnjbarton.com>, Scott Miles <sjmiles@google.com>, Rafael Weinstein <rafaelw@google.com>, public-webapps <public-webapps@w3.org>, Blake Kaplan <mrbkap@mozilla.com>, William Chen <wchen@mozilla.com>, Jonas Sicking <jonas@sicking.cc>, Steve Orvell <sorvell@google.com>, Dave Herman <dherman@mozilla.com>, Boris Zbarsky <bzbarsky@mit.edu>
Message-Id: <FF938EA4-EBEA-4BA0-A42D-C66524A40C29@wirfs-brock.com>
To: Dimitri Glazkov <dglazkov@google.com>
Since the original message was directed two me, I'm going to answer it directly rather than wading into the middle of the followup discussion. 

In reading the following, factor in that it is coming some somebody who doesn't know the details your requirements or your current design but someone who thinks they have an intuitive understanding of the problem you are trying to solve.  Also I have a general understanding of how HTML is parsed and transformed into DOM elements but I'm not an expert in that area.  I do understand at a deep level how JavaScript works and how to design object based software.

I can imagine that this naive perspective may actually be useful .

On Apr 12, 2013, at 12:30 PM, Dimitri Glazkov wrote:

> ... or "How the heck do we initialize custom elements in declarative syntax?"
> 
> There were good questions raised about the nature of <script> element
> in the "platonic form" thread. Consider this syntax:
> 
> <element name="foo-bar">
>    <script> ...</script>
>    <template> ... </template>
> </element>
> 

First I need to understand what you think an <element> tag actually means.  My assumption is that it is used to /define/ a new kind of html tag (identified by the name attribute) and associate it with the corresponding DOM node constructor that will create the corresponding DOM nodes whenever this new tag is encountered.  However, this element tag is not, itself, one of these new tags and is not represented in the DOM by an instance of the associated constructor.  Rather, an <element> tag is presumably represented in the DOM by an HTMLElementElement and child nodes such as an HTMLScriptElement and an HTMLTemplateElement.

It isn't until the HTML parser encounters a <foo-bar> tag that it actually creates a DOM element using the user provided constructor. 

So, based upon that understanding:

> The way <element> should work is like this:
> a) when </element> is seen
> b) generate a constructor for this element

The main thing that <element> really needs to do is make an association between the element name and the JS name that binds to a constructors. It also does related template things but those don't seem relevant to this immediate discussion.

The constructor could presumably have been created prior to encountering the <element>.  For example:

<script> /* code that defines a constructor named FooBar */</script>
<!-- an arbitrary amount of stuff -->
<element name="foo-bar" constructor="FooBar">
   <template> ... </template>
</element> 

It's only for (good) modularity reasons that you might want to place the <script> tag as a child of the <element> but the semantics should be the same in either case.  The really important thing is the association between the two strings "foo-bar" and "FooBar". 

Also, it isn't clear that the actual constructor has to be available until the first time a <foo-bar> tag is countered.  I can imagine that FooBar isn't resolved to a JS object until that time.  But, I also imagine that debugging will be easier if it is resolved when </element> is processed. 

> b) run document.register
What does this actually do?  Is it just registering the association between "foo-bar" and "FooBar" so that the HTML parser knows what to do when it encounters <foo-bar>? If so, why does this even need to be a public API rather than just part of the specified semantics of <element>?  Also, is it the the actual constructor object that needs to be registered or just its name?

> c) run initialization code

Initialization code for what? The <element> node? Is this something more than defining the constructor?  What sort of imperative initialization needs to take place in the middle of this declarative construct?

> 
> As I see it, the problem is twofold:
> 
> 1) The <script> element timing is weird. Since <script> is
> initialization code, it has to run after the </element> is seen. This
> is already contrary to a typical <script> element expectations.

This relatives to my comments for c) above.  What initialization are we talking about.  Defining a constructor named FooBar doesn't have to wait until the </element> is encountered.

If you explicitly make the association between the <element> name using an attribute like "constructor=FooBar" it isn't clear to me what other initialization code is needed.

I would really hope that the normal evaluation rules apply to script blocks nested within <element>.  Inline scripts evaluate immediately when encountered.  Non-inline scripts follow standard async load rules.

> 
> 2) The <script> element needs a way to refer to the custom element
> prototype it is initializing. Enclosing it in a function and calling
> it with <element> as |this| seemed like a simplest thing to do, but
> Rick and John had allergic reactions and had to be hospitalized.

This is where I gets fuzzy to me about whether you are talking about the <element> node or the <foo-bar> node.

In JavaScript, creating a prototype object is part of the process of defining a constructor. It seems like by "initializing" above you are referring to such a constructor definition process.  In that case, the prototype it is "initializing" is part of the constructor definition.  Doing this using ES5 it might look like:

//define FooBar abstraction
function FooBar() {
      /* constructor body, initializes FooBar instances when new FooBar is evaluated*/
 }
FooBar.prototype = Object.create(HTMLElement.prototype);  //prototype used by FooBar instances
FooBar.prototype.method1= function () {...};  //install methods in prototype

Using ES6 classes it would look like:

class FooBar extents HTMLElement {
   constructor () {/* constructor body, initializes FooBar instances */  }
   method1() { ...}
}

The ES5 and ES6 formulation mean the same thing.  ES6 is just more concise.

So far, no need to reference anything that isn't self contained within the actual constructor definition logic.

If you are saying there is sometimes also a need to perform arbitrary imperative actions on the DOM elements of <element name="foo-bar"> I have two answers.

First, its a DOM element and its has the name.  Write code that retrieves the DOM element by name and does whatever is necessary.

If this is a common enough occurrence that you want to avoid the name based retrieval, then define an interface on element constructors and pass the element node into it as an argument. for example:

function initializeElement(anElementElement) {
    // the argument is the HTMLElementElement that this constructor is bound to
    ...
}

In ES5 you would define this by augmenting the above example with:
FooBar.initializeElement = function (anElementElement) { ...};

In ES6 you just add the following to the class definition body:

static initializeElement(anElementElement) {...}


> 
> So far, I haven't seen any other workable alternatives. TC39 peeps and
> others, help me find them.

There you have it.

Allen





> 
> :DG<
> 
Received on Saturday, 13 April 2013 00:42:23 UTC

This archive was generated by hypermail 2.3.1 : Wednesday, 5 February 2014 07:18:49 UTC