> 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.