Re: Model-driven Views

> From: Rafael Weinstein<rafaelw@google.com  <mailto:rafaelw@google.com?Subject=Re%3A%20Model-driven%20Views&In-Reply-To=%253CBANLkTimZcXiO8U8xHg2Y6eT4igixkCiF7w%40mail.gmail.com%253E&References=%253CBANLkTimZcXiO8U8xHg2Y6eT4igixkCiF7w%40mail.gmail.com%253E>>  
> It sounds like the group wants to proceed by looking first at missing
> primitives. Maciej is right that one of them is the ability to declare
> inert DOM structures,
In XForms we use the /instance/ element to do this.

> but my feeling is that it's probably best to
> start with the central problem:
>
> -There's no way to observe mutations to JS objects.

I'll give some comments on this second question, but first I'll go 
through how we do "inert DOM structures" using the instance element.

Of course, in spec-land we do it by describing the /instance element 
/and its interactions with expressions referring to it, and with DOM 
events, but in the context of the current discussion about adding new 
webapps support, it's important to take a look at how it's achieved in 
today's client-side implementations of XForms in common desktop browsers.

There are two main approaches:  The AgenceXML implementation uses an 
XSLT PI at the top to parse out the XForms namespaced elements, and when 
xhtml:head/xforms:model/xforms:instance with element content is seen, 
it's converted into a DOM object.
The Ubiquity XForms implementation uses a purely Javascript-based 
approach to this transformation, without using the XSLT PI, but it 
suffers the treatment non-HTML elements receive when appearing lexically 
in the host document DOM.

As far as feature definition of the "inert DOM structure" goes, here's a 
brief overview of how we use instance in XForms, and then I'll segue 
into how separating data and presentation makes the mutation observation 
question solvable.

Additionally, note that XForms allows for an @src attribute on instance 
which specifies a resource to be loaded; there's a DOM event which 
signals that it's time to do this, and the XForms processor responds to 
that event.

Finally, XForms offers a /submission/ element which can submit data from 
an ID'd instance, and send data from responses back to instances or 
parts thereof.

For example,

<html>
<head>
<model>
<instance>
<quote>
<color>red</color>
<size>3</size>
<quote>
</instance>
<submission id="price" resource="/rfq" method="post" replace="instance" />
</model>
</head>
<body>
<input ref="color">
<label>Color: </color>
</input>
<input ref="size">
<label>Size: </label>
</input>
<output ref="price">
<label>Your price: </label>
</output>
<submit submission="buy">
<label>Buy</label>
</submit>
</body>
</html>

When the page is loaded, the instance XML will get initialized with the 
data.
When the user interacts with the form controls, changes will be commited 
to the instance data.
When the user presses the submit button labeled Buy, the submission will 
POST the instance data.
Since the submission says replace instance, the submitted instance will 
be replaced.

Note that the initial data contains no price, so the output bound to 
"price" will not display at all; it's considered /irrelevant/.
When the response comes back, if it has a price, it will then display, 
along with its label.

Let's say you wanted to split the request and response into two 
different pieces of XML and not share them in the page.
Just add a second instance, and now put ID attributes on the two:

<html>
<head>
<model>
<instance id="rfq">
<quote>
<color>red</color>
<size>3</size>
<quote>
</instance>
<instance id="quote"><empty /></instance>
<submission id="price" resource="/rfq" method="post" 
ref="instance('rfq') replace="instance" target="quote" />
</model>
</head>
<body>
</body>
<input ref="color">
<label>Color: </color>
</input>
<input ref="size">
<label>Size: </label>
</input>
<output ref="instance('quote')/price">
<label>Your price: </label>
</output>
<submit submission="buy">
<label>Buy</label>
</submit>
</body>
</html>

Note the changes in the submission to show that the data from the "rfq" 
instance and the response goes to "quote."
If some sub-part of the instance were to be submitted, it would be done 
inside that expressions.

Now, let's assume that performing a request for a quote is idempotent 
and has no side effects.   REST web architecture would have us use a GET 
instead of a POST.  XForms uses sensible defaults, so the GET will 
serialize leaf-node data as application/x-www-url-formencoded, so all we 
need to do is change the method on the submission from POST to GET:

<html>
<head>
<model>
<instance id="rfq">
<quote>
<color>red</color>
<size>3</size>
<quote>
</instance>
<instance id="quote"><empty /></instance>
<submission id="price" resource="/rfq" method="get" ref="instance('rfq') 
replace="instance" target="quote" />
</model>
</head>
<body>
</body>
<input ref="color">
<label>Color: </color>
</input>
<input ref="size">
<label>Size: </label>
</input>
<output ref="instance('quote')/price">
<label>Your price: </label>
</output>
<submit submission="buy">
<label>Buy</label>
</submit>
</body>
</html>

Let's move the initial order out of the form and into a resource on the 
server by changing instance to have a src attribute:

<html>
<head>
<model>
<instance id="rfq" src="initial-quote.xml" />
<instance id="quote"><empty /></instance>
<submission id="price" resource="/rfq" method="get" ref="instance('rfq') 
replace="instance" target="quote" />
</model>
</head>
<body>
</body>
<input ref="color">
<label>Color: </color>
</input>
<input ref="size">
<label>Size: </label>
</input>
<output ref="instance('quote')/price">
<label>Your price: </label>
</output>
<submit submission="buy">
<label>Buy</label>
</submit>
</body>
</html>
where initial-quote.xml has the obvious content, and is served up with 
an HTTP GET.

Let's flesh out the data layer a bit with some datatypes and 
constraints.  Let's say size is an integer and it's constrained to be 0 
to 14.
We do this declaratively by binding a datatype and a constraint to the 
size node.
Furthermore, let's make sure that both color and size aren't empty with 
required="true()":


<html>
<head>
<model>
<instance id="rfq" src="initial-quote.xml" />
<bind ref="instance('rfq')">
<bind ref="size" type="integer" constraint=". >= 0 and . &lt;= 14" 
required="true()" />
<bind ref="color"required="true()" />
</bind>
<instance id="quote"><empty /></instance>
<submission id="price" resource="/rfq" method="get" ref="instance('rfq') 
replace="instance" target="quote" />
</model>
</head>
<body>
</body>
<input ref="color">
<label>Color: </color>
</input>
<input ref="size">
<label>Size: </label>
</input>
<output ref="instance('quote')/price">
<label>Your price: </label>
</output>
<submit submission="buy">
<label>Buy</label>
</submit>
</body>
</html>

Now the form won't submit (and if you try will raise an 
xforms-submit-error DOM event with the context information), and any 
form controls bound to the invalid nodes will receive xforms-invalid 
events when the user interacts with them.  Furthermore, those controls 
will also get CSS pseudo-property :invalid.  (They're also similarly 
have :required and xforms-required events).

So this interestingly answers Rafael's questions below about mutations.  
In XForms, the mutations happen to the data model, but the observation 
of the mutations happens at the presentation level, and is reflected in 
the HTML world as DOM events and CSS presentation style attributes.  
Using DOM Events (or the XML syntax isomorphism XML Events), you can 
place listeners on the form controls bound to the data nodes, and run 
actions when the events happen.
> Current approaches resort to a number of hacks which create really terrible
> artifacts in application code. The ECMA Script Proxy mechanism may be the
> best starting point, but there's a related
> problem with timing that isn't addressed.
>

Leigh.

Received on Tuesday, 10 May 2011 17:35:27 UTC