Author: jkuhnert
Date: Sun Jun 24 19:24:19 2007
New Revision: 550318
URL: http://svn.apache.org/viewvc?view=rev&rev=550318
Log:
Added some more documentation.
Added:
tapestry/tapestry4/trunk/src/site/apt/ajax/basics.apt
tapestry/tapestry4/trunk/src/site/apt/ajax/json.apt
Modified:
tapestry/tapestry4/trunk/src/site/apt/ajax/responsebuilder.apt
tapestry/tapestry4/trunk/src/site/site.xml
tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/WEB-INF/Home.page
Added: tapestry/tapestry4/trunk/src/site/apt/ajax/basics.apt
URL:
http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/src/site/apt/ajax/basics.apt?view=auto&rev=550318
==============================================================================
--- tapestry/tapestry4/trunk/src/site/apt/ajax/basics.apt (added)
+++ tapestry/tapestry4/trunk/src/site/apt/ajax/basics.apt Sun Jun 24 19:24:19
2007
@@ -0,0 +1,239 @@
+ ------
+Ajax/DHTML Guide - Basics
+ ------
+Jesse Kuhnert
+ ------
+24 June 2007
+ ------
+
+Ajax Development Basics
+
+ The overall concept behind the majority of built in AJAX functionality in
Tapestry is that everything should work exactly the same way in your
pages/components
+ in AJAX requests as it does during normal page rendering. There are a few
corner cases that have no real intuitive XHR equivalent which we will discuss as
+ well.
+
+* Including JavaScript in your HTML Template
+
+ One of the first things you will need to do is make sure you have the
necessary javascript includes. Tapestry makes extensive use of the
{{{http://dojotoolkit.org}Dojo}}
+ javascript toolkit and provides a couple of options for including it for you
automatically via the {{{../components/general/shell.html}Shell}} or
+ {{{../components/general/scriptincludes.html}ScriptIncludes}} components.
An example of defining a basic outer layer using the
{{{../components/general/shell.html}Shell}}
+ component would look like:
+
++-------------------------------------------------------------------------------------------------------------
+<html jwcid="@Shell" title="Basic Ajax Page">
+<body jwcid="@Body">
+
+ <p>Basic javascript inclusion sample.</p>
+
+</body>
+</html>
++-------------------------------------------------------------------------------------------------------------
+
+* Updating Components: The universal updateComponents parameter
+
+ One of the most frequently used pieces of ajax functionality is the
<<<updateComponents="foo,bar">>> parameter that is now implemented by all of
the core Tapestry
+ components - where it makes sense. Some of the more familiar of these are
{{{../components/link/directlink.html}DirectLink}},
{{{../components/form/linksubmit.html}LinkSubmit}},
+ {{{../components/form/form.html}Form}},
{{{../components/form/imagesubmit.html}ImageSubmit}} and
{{{../components/form/submit.html}Submit}}. We'll build on our previous
+ example and use the {{{../components/link/directlink.html}DirectLink}}
component to refresh a current time display.
+
++-------------------------------------------------------------------------------------------------------------
+..
+<p>Basic javascript inclusion sample.</p>
+
+<p>
+ <a jwcid="@DirectLink" listener="listener:setTime"
updateComponents="time">Refresh time</a>.
+</p>
+
+<div jwcid="[EMAIL PROTECTED]" value="ognl:time" renderTag="true" />
+..
++-------------------------------------------------------------------------------------------------------------
+
+ The corresponding java page class:
+
++-------------------------------------------------------------------------------------------------------------
+..
+public abstract BasicAjax extends BasePage {
+
+ public abstract void setTime(Date time);
+
+ public void setTime()
+ {
+ setTime(new java.util.Date());
+ }
+}
++-------------------------------------------------------------------------------------------------------------
+
+ That's it! Building and running this small example should be all you need
to start using the very basic AJAX functionality provided by Tapestry.
+
+** updateComponents == IComponent.getClientId()
+
+ There are a few subtle things happening here that may not be apparent at
first glance. One of the more important changes in Tapestry 4.1.x was the
addition of
+ the
{{{../apidocs/org/apache/tapestry/IComponent.html#getClientId()}IComponent.getClientId()}}
method to all component classes. When dealing with client side javascript
+ functionality the unique client side element id of the html elements your
components generate becomes very important. What this method gives you is the
unique element id
+ attribute that will be written for any given component depending on the
context in which you call it.
+
+ If you are in a {{{../components/general/for.html}For}} loop then the
<<<clientId>>> will automatically be incremented for each loop and always
represent the unique
+ value of the component. The same semantics work whether in
{{{../components/form/form.html}Forms}} /
{{{../components/general/for.html}For}} or any other nested
+ type of structure. This has been one of key changes in the API that has
made the AJAX functionality provided <much> easier to work with.
+
+ In our example the <<<updateComponents>>> parameter given made use of the
new smart auto binding functionality added to the framework recently, but you
could write
+ the equivalent parameter using an ognl expression as well:
+
++-------------------------------------------------------------------------------------------------------------
+<a jwcid="@DirectLink" listener="listener:setTime"
+ updateComponents="ognl:components.time.clientId">Refresh time</a>
++-------------------------------------------------------------------------------------------------------------
+
+ Things can get a lot uglier once you start trying to update multiple
components:
+
++-------------------------------------------------------------------------------------------------------------
+<a jwcid="@DirectLink" listener="listener:setTime"
+ updateComponents="ognl:{components.time.clientId,
page.components.status.clientId}">Refresh time</a>
++-------------------------------------------------------------------------------------------------------------
+
+ The much easier simple string list <<<updateComponents="compA, compB,
compC">>> is not only shorter / easier to understand but also more efficient
and robust
+ in many different circumstances. For instance, the auto binding
functionality would be able to detect that you were trying to update a
component contained within
+ one of your own custom components as well as a component on the page
containing your component and wire up and find all the correct <<<clientId>>>
values for you
+ automatically.
+
+ The thing to walk away with here is that in 98% of the cases you run in to
this style of <<<updateComponents>>> syntax is the way to go:
+
++-------------------------------------------------------------------------------------------------------------
+<a jwcid="@DirectLink" listener="listener:setTime"
updateComponents="time,status">Refresh time</a>
++-------------------------------------------------------------------------------------------------------------
+
+** How clientId values are generated
+
+ The actual value that is output by a component is determined by a number of
rules depending on the context and type of component involved. Of course, no
id will
+ be generated at all if your custom components don't call the new
+
{{{../apidocs/org/apache/tapestry/AbstractComponent.html#renderIdAttribute(org.apache.tapestry.IMarkupWriter,%20org.apache.tapestry.IRequestCycle)}AbstractComponent.renderIdAttribute(IMarkupWriter,
IRequestCycle)}}
+ method provided in the base <<<AbstractComponent>>> superclass that most
components derive from. When that method is called the rules for determining
what value to render out
+ in to the generated html element are evaluated in this order:
+
+ [[1]] <<informal id parameter bound?>> - If the definition of the component
being rendered had an informal <<<id="UpdatedComponent">>> parameter specified
that will
+ take higher precendence than anything else. It may eventually end up being
output as <<<id="UpdatedComponent_4">>> if your component is being rendered in
a loop but
+ it will still use the id as the basis for its client side id values.
+
++----------------------------------------------
+<div jwcid="@Any" id="customId" >Content</div>
++----------------------------------------------
+
+ [[1]] <<use IComponent.getId() value>> - If no informal id parameter is
bound then the next candidate used to seed the clientIds is the value returned
from
+ <<<IComponent.getId()>>>. This will be the id specified in your
<<<Page.page>>> file or the id assigned in your html snippet as in:
+
++----------------------------------------------
+<div jwcid="[EMAIL PROTECTED]">Content</div>
+
+or .page file:
+
+<component id="customId" type="Any">
+</component>
++----------------------------------------------
+
+ [[1]] <<form component?>> - If the component in question implements
{{{../apidocs/org/apache/tapestry/form/IFormComponent.html}IFormComponent}}
then the same semantics
+ as above apply with the caveat that the inner workings of client id
generation work slightly differently here as they are synchronized back with
the normal
+ {{{../components/form/form.html}Form}} input name/clientId semantics that
Tapestry has always had.
+
+ []
+
+ The previously mentioned <<<renderIdAttribute>>> method is the crucual piece
that anyone writing custom components will need to make sure to call if you
plan on
+ overriding <<<IComponent.renderComponent>>>. One very simplistic example of
calling this new base method would be:
+
++-------------------------------------------------------------------------------------------------------------
+..
+public abstract MyCustomComponent extends AbstractComponent {
+
+ public void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ writer.begin("div");
+
+ writer.renderIdAttribute(writer, cycle);
<----------------------------------*
+ writer.renderInformalParamters(writer, cycle);
+
+ writer.print("Custom text content..");
+
+ writer.end();
+ }
+}
++-------------------------------------------------------------------------------------------------------------
+
+** Common Mistake: Can't update a component not already rendered on the page
+
+ One of more common questions on the users mailing list inevitably involves
this innocent looking block of code modified from our previous examples:
+
++-------------------------------------------------------------------------------------------------------------
+..
+<p>Basic javascript inclusion sample.</p>
+
+<p>
+ <a jwcid="@DirectLink" listener="listener:setTime"
updateComponents="time">Refresh time</a>.
+</p>
+
+<span jwcid="@If" condition="ognl:time != null">
+ <div jwcid="[EMAIL PROTECTED]" value="ognl:time" renderTag="true" />
+</span>
+..
++-------------------------------------------------------------------------------------------------------------
+
+ The problem with this code is that the component being requested to update -
<<<time>>> - doesn't actually exist as a valid client side
+ html element when you click on the <<<Refresh Time>>> link. The initial
block of html generated from this template will really look
+ like:
+
++-------------------------------------------------------------------------------------------------------------
+..
+<p>Basic javascript inclusion sample.</p>
+
+<p>
+ <a href="http://localhost:80080/app?..." id="DirectLink"
+ onClick="return tapestry.linkOnClick(this.href)">Refresh time</a>.
+</p>
+..
++-------------------------------------------------------------------------------------------------------------
+
+ The crucial piece missing in the above sample snippet is an html block for
our <<<time>>> component. It wasn't rendered initially because
+ the time value was null. The semantics of how the client side XHR io logic
works basically comes down to:
+
+ * <<make XHR IO Request>> - Initiate client side XHR request which will
eventually return a block of html for our <<<time>>> component
+ of:
+
++-------------------------------------------------------------------------------------------------------------
+<div id="time">10:49 am 1/2/2007</div>
++-------------------------------------------------------------------------------------------------------------
+
+ * <<find the matching client side dom node and update it>> - This is the
part that breaks down. The very basic minimal example of how it is
+ done would look something like:
+
++-------------------------------------------------------------------------------------------------------------
+..
+var responseNode = getResponseHtml(); // this is the time div block from the
last example
+
+var updateNode = document.getElementById("time");
+updateNode.innerHTML = getContentAsString(responseNode);
+..
++-------------------------------------------------------------------------------------------------------------
+
+ The resulting client side error that is logged should be something along the
lines of <<<"couldn't find a matching client side dom node to update with id of
'time'">>>.
+
+ The solution to get around this varies for each situation but the most
common / easiest method is usually done by just wrapping the block that
conditionally renders
+ with a simple {{{../components/general/any.html}Any}} component:
+
++-------------------------------------------------------------------------------------------------------------
+..
+<p>Basic javascript inclusion sample.</p>
+
+<p>
+ <a jwcid="@DirectLink" listener="listener:setTime"
updateComponents="updateArea">Refresh time</a>.
+</p>
+
+<span jwcid="[EMAIL PROTECTED]">
+ <span jwcid="@If" condition="ognl:time != null">
+ <div jwcid="[EMAIL PROTECTED]" value="ognl:time" renderTag="true" />
+ </span>
+</span>
+..
++-------------------------------------------------------------------------------------------------------------
+
+ Now the example specifies a <<<updateComponents="updateArea">>> definition
for which components to update and has the
{{{../components/general/any.html}Any}} updated
+ instead of <<<time>>> directly as it <will> exist on the client side prior
to our requesting an update on it and shouldn't interfere overly much with
whatever
+ CSS/design we have setup.
+
\ No newline at end of file
Added: tapestry/tapestry4/trunk/src/site/apt/ajax/json.apt
URL:
http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/src/site/apt/ajax/json.apt?view=auto&rev=550318
==============================================================================
--- tapestry/tapestry4/trunk/src/site/apt/ajax/json.apt (added)
+++ tapestry/tapestry4/trunk/src/site/apt/ajax/json.apt Sun Jun 24 19:24:19 2007
@@ -0,0 +1,136 @@
+ ------
+Ajax/DHTML Guide - JSON (JavaScript Object Notation)
+ ------
+Jesse Kuhnert
+ ------
+24 June 2007
+ ------
+
+JSON - JavaScript Object Notation
+
+ JSON is a very popular data transmission format used in many XHR
applications - the ever popular gmail application makes extensive use of it.
The official
+ web site has much more information on all the details at {{http://json.org}}.
+
+ While the majority of default XHR (ajax) behaviour provided by Tapestry uses
+ XML as the exchange format - because of how easy it is to wrap existing html
blocks - it does also provide a great deal of support/API functionality for you
+ to utilize JSON data transmission formats in your own applications.
+
+ This document will go over some of the basics of using this part of the API
on the client / server side as well as show examples of how it is used by
Tapestry
+ itself in components such as the
{{{../components/dojo/autocompleter.html}Autocompleter}}.
+
+* JSON Basics
+
+ The basic idea behind the JSON format is that you can output something using
it from the server and your browser can evaluate it and access the structures
you
+ define directly - like any other javascript object. For example, we could
define a response listing the general attributes of a user in some theoretical
system
+ we are building:
+
++--------------------------------------------------------------------------------------
+{name:"Dr. No", occupation:"Super villain", age:52, email:"[EMAIL PROTECTED]"}
++--------------------------------------------------------------------------------------
+
+ The above block of JSON can be interpreted and used on the client side in
javascript with a couple simple statements:
+
++--------------------------------------------------------------------------------------
+var user=eval("{name:'Dr No', occupation: 'Super villain'}");
+alert("User occupation:" + user.occupation + " name:" + user.name);
++--------------------------------------------------------------------------------------
+
+ The format also supports returning array like structures as well as nesting
of differnt kind of structures as in:
+
++--------------------------------------------------------------------------------------
+{users:[
+ {name:"Dr. No", occupation:"Super villain", age:52, email:"[EMAIL
PROTECTED]"},
+ {name:"Henrietta Smith"},
+ {name:"Lev Nikolayevich Myshkin", occupation:"Idiot"}
+ ]
+}
+
+or just..:
+
+["10", "30", "14", "5"]
++--------------------------------------------------------------------------------------
+
+ You get the idea.. One of the more useful things provided by
{{http://json.org}} is a sample java API for working with the format -
{{http://www.json.org/java/index.html}}.
+ Tapestry has incorporated this java code in to the API proper <(with some
minor improvements)> so creating our first example JSON object output can be as
simple as:
+
++-----------------------------------------------------------------------------------------------------------------
+..
+org.apache.tapestry.json.JSONObject json = new JSONObject();
+json.put("name", "Dr. No");
+json.put("occupation", "Super villain");
+..
+calling json.toString() should produce:
+{"name":"Dr. No", "occupation":"Super villain"}
++-----------------------------------------------------------------------------------------------------------------
+
+ <<See also:>>
{{{../apidocs/org/apache/tapestry/json/package-summary.html}Tapestry JSON API}}
+
+* {{{../apidocs/org/apache/tapestry/IJSONRender.html}IJSONRender}}: Writing
JSON capable components
+
+ To support this new format we've added a new optional interface that you can
implement in your components -
{{{../apidocs/org/apache/tapestry/IJSONRender.html}IJSONRender}}:
+
++-----------------------------------------------------------------------------------------------------------------
+public void renderComponent(IJSONWriter writer, IRequestCycle cycle)
+{
+}
++-----------------------------------------------------------------------------------------------------------------
+
+ The basic idea is the same as the typical
<<<IComponent.renderComponent(IMarkupWriter, IRequestCycle)>>> call - except
that in this case you are dealing with
+ a {{{../apidocs/org/apache/tapestry/json/IJSONWriter.html}IJSONWriter}}
instance instead of the more familiar
{{{../apidocs/org/apache/tapestry/IMarkupWriter.html}IMarkupWriter}}.
+ This interface is really just a wrapper around the JSON api provided by
Tapestry.
+
+ Once you have implemented this
{{{../apidocs/org/apache/tapestry/IJSONRender.html}IJSONRender}} interface in
one of your components that is pretty much all there is to
+ do. The JSON method you implement will only be called if a JSON request is
processed by Tapestry <AND> the request has specified your component as one of
the components
+ to update and capture the response of. Otherwise the normal html markup
based methods will be called on your component.
+
+** Client Side Processing
+
+ Processing json response data on the client side isn't really something
Tapestry can do for you, so you'll have to have your own consumer of this data
set up
+ to handle it beforehand. If you use the standard Tapestry API's then the
global {{{../jsdoc/files/core-js.html#tapestry.loadJson}tapestry.loadJson}}
javascript function
+ will be invoked. Currently this function does nothing other than decrement
the global <<<tapestry.requestsInFlight>>> javascript variable value. You can
replace or
+ do an event connection on this function to provide your own implementation.
An example of doing an event connection would be:
+
++-----------------------------------------------------------------------------------------------------------------
+dojo.event.connect(tapestry, "loadJson", function(type, data, http, kwArgs){
+ // do your stuff...the data argument is your json object
+});
++-----------------------------------------------------------------------------------------------------------------
+
+* Example: Tapestry's Autocompleter Component
+
+ One example of a core Tapestry component that implements the
{{{../apidocs/org/apache/tapestry/IJSONRender.html}IJSONRender}} interface is
the
+ {{{../components/dojo/autocompleter.html}Autocompleter}} component. The
method implementation of that component looks like this:
+
++-----------------------------------------------------------------------------------------------------------------
+public void renderComponent(IJSONWriter writer, IRequestCycle cycle)
+{
+ IAutocompleteModel model = getModel();
+
+ if (model == null)
+ throw Tapestry.createRequiredParameterException(this, "model");
+
+ List filteredValues = model.getValues(getFilter());
+
+ if (filteredValues == null)
+ return;
+
+ Object key = null;
+ String label = null;
+ JSONObject json = writer.object();
+
+ for (int i=0; i < filteredValues.size(); i++) {
+ Object value = filteredValues.get(i);
+
+ key = model.getPrimaryKey(value);
+ label = model.getLabelFor(value);
+ json.put(getDataSqueezer().squeeze(key), label );
+ }
+}
++-----------------------------------------------------------------------------------------------------------------
+
+ This component actually makes use of some new base classes - like
{{{../apidocs/org/apache/tapestry/dojo/form/AbstractFormWidget.html}AbstractFormWidget}}
- don't
+ let this distract you from the <<<IJSONRender>>> interface portion. They
are both mutually exclusive and totally unrelated.
+
+ That is pretty much it. This component hands off the majority of client
side functionality to a Dojo widget and only provides the widget with a URL
string produced
+ by a pre-generated JSON request. You can find the full source and all of
the gory details of the rest of
+ that
{{{http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/dojo/form/Autocompleter.java?view=markup}here}}.
Modified: tapestry/tapestry4/trunk/src/site/apt/ajax/responsebuilder.apt
URL:
http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/src/site/apt/ajax/responsebuilder.apt?view=diff&rev=550318&r1=550317&r2=550318
==============================================================================
--- tapestry/tapestry4/trunk/src/site/apt/ajax/responsebuilder.apt (original)
+++ tapestry/tapestry4/trunk/src/site/apt/ajax/responsebuilder.apt Sun Jun 24
19:24:19 2007
@@ -43,7 +43,7 @@
@EventListener(targets = "projectChoose", events = "onValueChanged")
public void projectSelected(IRequestCycle cycle)
{
- cycle.getResponseBuilder().updateComponent("myComponentId");
+ cycle.getResponseBuilder().updateComponent("myComponentId");
}
....
Modified: tapestry/tapestry4/trunk/src/site/site.xml
URL:
http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/src/site/site.xml?view=diff&rev=550318&r1=550317&r2=550318
==============================================================================
--- tapestry/tapestry4/trunk/src/site/site.xml (original)
+++ tapestry/tapestry4/trunk/src/site/site.xml Sun Jun 24 19:24:19 2007
@@ -82,19 +82,17 @@
<item name="Development Guide" href="/developmentguide/index.html"
collapse="true">
<item name="Exception Pages"
href="/developmentguide/exceptionpages.html" />
<item name="Testing" href="/developmentguide/testing.html" />
-
- <item name="HiveMind Services"
href="/developmentguide/hivemind/index.html" >
- <item name="Rounded Corners"
href="/developmentguide/hivemind/roundedcorners.html" />
- </item>
-
+ <item name="Rounded Corners"
href="/developmentguide/hivemind/roundedcorners.html" />
<item name="Performance Tuning"
href="/developmentguide/performancetuning.html" />
</item>
<item name="XHR/DHTML Guide" href="/ajax/index.html"
collapse="true">
<item name="Introduction" href="/ajax/index.html" />
+ <item name="Basics" href="/ajax/basics.html" />
<item name="Debugging" href="/ajax/debugging.html" />
<item name="EventListener" href="/ajax/eventlistener.html" />
<item name="ResponseBuilder" href="/ajax/responsebuilder.html"
/>
+ <item name="JSON" href="/ajax/json.html" />
</item>
<item name="JavaScript Reference" href="/javascript/index.html"
collapse="true">
Modified:
tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/WEB-INF/Home.page
URL:
http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/WEB-INF/Home.page?view=diff&rev=550318&r1=550317&r2=550318
==============================================================================
---
tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/WEB-INF/Home.page
(original)
+++
tapestry/tapestry4/trunk/tapestry-examples/TimeTracker/src/context/WEB-INF/Home.page
Sun Jun 24 19:24:19 2007
@@ -22,6 +22,6 @@
<page-specification class="org.apache.tapestry.timetracker.page.TaskEntryPage">
<property name="dlHidden" initial-value="true" />
-
+
</page-specification>