Author: hlship
Date: Fri Nov 24 12:03:47 2006
New Revision: 478972
URL: http://svn.apache.org/viewvc?view=rev&rev=478972
Log:
More random thoughts ...
Modified:
tapestry/tapestry5/tapestry-project/trunk/src/site/resources/tap5devwiki.html
tapestry/tapestry5/tapestry-project/trunk/src/site/resources/tap5devwiki.xml
Modified:
tapestry/tapestry5/tapestry-project/trunk/src/site/resources/tap5devwiki.html
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-project/trunk/src/site/resources/tap5devwiki.html?view=diff&rev=478972&r1=478971&r2=478972
==============================================================================
---
tapestry/tapestry5/tapestry-project/trunk/src/site/resources/tap5devwiki.html
(original)
+++
tapestry/tapestry5/tapestry-project/trunk/src/site/resources/tap5devwiki.html
Fri Nov 24 12:03:47 2006
@@ -5165,31 +5165,38 @@
<div id="saveTest"></div>
<div id="contentWrapper"></div>
<div id="contentStash"></div>
- <div id="storeArea"><div tiddler="ComponentActionRequest"
modifier="HowardLewisShip" modified="200610081335" created="200610080113"
tags="">Component actions are actions that reflect user interaction with a
component within a page. Again, this falls into several broad categories:\n\n*
Links that perform a server-side action, and result in a page refresh, or a new
page being displayed.\n* Ajax style links, which perform a server-side action,
and refresh only part of the page.\n* Forms which perform a server-side action,
followed by a page refresh (or new page being displayed).\n* Ajax style forms,
which trigger an action, followed by a refresh of part of the page.\n* Other
user interactions, which result in a server side action, and a partial page
refresh.\n\nIn all of these cases, one or more ComponentEvents is fired. The
result of ComponentEvent determines whether a partial page render or a full
page render occurs.\n\nIn the later case, a client side redirect is sent, to f
orce the browser to initial a new PageRenderRequest. This addresses an issue
in Tapestry 4, in that following a link or form submission, the URL would
indicate details about the previous page, not the newly displayed page, and
clicking the browser refresh button could cause a server side operation to
occur again (which would often be quite undersirable).\n\n!URI
Format\n\n{{{\n/page-name.event-type/component-id-path/id\n}}}\n\nHere
page-name is the LogicalPageName. The event-type is a string that identifies
the type of event (and will ultimately be used to select an event handler
method). \n\nThe component-id-path is a dot-separated series of component ids,
used to identify a specific component within the overall page.\n\nThe id is
optional, and may be repeated. The id value or values will be provided to the
event handler method.\n\nExample: /Login.submit/form (the URI for a form
component on page Login).\n\nExample: /admin/UserProfile/action/menu.delete/37
(component m
enu.delete of the UserProfile page, with an id of 37).\n</div>
+ <div id="storeArea"><div tiddler="Assets" modifier="HowardLewisShip"
modified="200611241951" created="200611241951" tags="">The concept of Assets is
unchanged from Tapestry 4.\n\nThere will be an "exclusion list" of
file name extensions. Files with that extension will require an md5sum as part
of the URI. The default exclusion list with be ".class". For other
file types (particularily .js, .css, etc.) the file will be the file, and
relative file names will work.\n\nSome kind of hook will be necessary to
support schemes like Akamai, where assets are stored externally. This should
also take into account localization.\n\nSome kind of folder aliasing will be
necessary. I may want "/assets/dojo/" to map on the classpath to
"/org/apache/tapestry/dojo/dojo_0_0_4/". When handling updates to
big packages such as Dojo, I don't want to have to sort through some ungodly
number of changes between 0.0.4 and 0.0.5, I want to create a new dojo_0_0
_5 folder, drop the contents in, delete the dojo_0_0_4 folder, and update the
mapping of "/assets/dojo/".</div>
+<div tiddler="ComponentActionRequest" modifier="HowardLewisShip"
modified="200610081335" created="200610080113" tags="">Component actions are
actions that reflect user interaction with a component within a page. Again,
this falls into several broad categories:\n\n* Links that perform a server-side
action, and result in a page refresh, or a new page being displayed.\n* Ajax
style links, which perform a server-side action, and refresh only part of the
page.\n* Forms which perform a server-side action, followed by a page refresh
(or new page being displayed).\n* Ajax style forms, which trigger an action,
followed by a refresh of part of the page.\n* Other user interactions, which
result in a server side action, and a partial page refresh.\n\nIn all of these
cases, one or more ComponentEvents is fired. The result of ComponentEvent
determines whether a partial page render or a full page render occurs.\n\nIn
the later case, a client side redirect is sent, to force the browser to i
nitial a new PageRenderRequest. This addresses an issue in Tapestry 4, in
that following a link or form submission, the URL would indicate details about
the previous page, not the newly displayed page, and clicking the browser
refresh button could cause a server side operation to occur again (which would
often be quite undersirable).\n\n!URI
Format\n\n{{{\n/page-name.event-type/component-id-path/id\n}}}\n\nHere
page-name is the LogicalPageName. The event-type is a string that identifies
the type of event (and will ultimately be used to select an event handler
method). \n\nThe component-id-path is a dot-separated series of component ids,
used to identify a specific component within the overall page.\n\nThe id is
optional, and may be repeated. The id value or values will be provided to the
event handler method.\n\nExample: /Login.submit/form (the URI for a form
component on page Login).\n\nExample: /admin/UserProfile/action/menu.delete/37
(component menu.delete of the Use
rProfile page, with an id of 37).\n</div>
+<div tiddler="ComponentDocumentation" modifier="HowardLewisShip"
modified="200611231534" created="200611231533" tags="parameters">Tapestry needs
a JavaDoc-like, or JavaDoc-based tool to extract documentation about component
parameters (and mixins) and present it in a useable format. In Tapestry 4, you
could document the public getters and setters, but in Tapestry 5, the
annotations are on the private instance variables, which are generally not
documented.</div>
<div tiddler="ComponentEvent" modifier="HowardLewisShip"
modified="200610081359" created="200610081351" tags="requests events">Component
events represent the way in which incoming requests are routed to user-supplied
Java methods.\n\nComponent events //primarily// originate as a result of a
ComponentActionRequest, though certain other LifecycleEvents will also
originate component events.\n\nEach component event contains:\n* An event type;
a string that identifies the type of event\n* An event source; a component that
orginates the event (where applicable)\n* A context; an array of strings
associated with the event\n\nEvent processing starts with the component that
originates the event.\n\nHandler methods for the event within the component are
invoked.\n\nIf no handler method aborts the event, then handlers for the
originating component's container are invoked.\n\nThis containues until
handlers for the page (the root component) are invoked, or until some handler
method aborts
the event.\n\nThe event is aborted when a handler method returns a non-null,
non-void value. The interpretation of that value varies based on the type of
event.\n\nEvents are routed to handler methods using the @~OnEvent
annotation.\n\nThis annotation is attached to a method within a component
class. This method becomes a handler method for an event.\n\nThe annotation
allows events to be filtered by event type or by originating
component.\n\n{{{\n @OnEvent(value="submit",
component="form")\n String handleSubmit()\n {\n // . . .\n\n
return "PostSubmit";\n }\n}}}\n\nIn the above hypothetical example,
a handler method is attached to a particular component's submit event. After
processing the data in the form, the LogicalPageName of another page within the
application is returned. The client browser will be redirected to that
page.\n\nHandler methods need not be public; they are most often package
private (which facilitated UnitTesting
of the component class).\n\nHandler methods may take parameters. This is most
useful with handler methods related to links, rather than forms.\n\nAssociated
with each event is the context, a set of strings defined by the application
programmer.\n\nParameters are coerced (see TypeCoercion) from these strings.
Alternately, a parameter of type String[] receives the set of strings.\n\n{{{\n
@OnEvent(component="delete")\n String deleteAccount(long
accountId)\n {\n // . . .\n\n return "AccountPage";\n
}\n}}}\n\nHere, ther first context value has been coerced to a long and passed
to the deleteAccount() method. Presemuable, an action link on the page, named
"delete", is the source of this event.\n\n</div>
<div tiddler="ComponentMixins" modifier="HowardLewisShip"
modified="200610051243" created="200610051234" tags="mixins">One of the more
exciting ideas in Tapestry 5 is //mixins//; the ability to add behavior to a
component without writing code. \n\nIt is expected that much common behavior,
especially for form control components, will be provided by mixins. Further,
many Ajax techniques will take the form of mixins applied to otherwise ordinary
components.\n\nA mixin is an additional component class that operates //with//
the main component. For a component element within the page, the functionality
is provided by the main component class and by\nthe mixin. \n\nMixins are
primarily about rendering. Mixin render methods are //mixed in// to the
components' render methods. In effect, the different rendering phases of a
component are different AOP-like //joinpoints//, and the mixins can provide
//before advice//.\n\nMixins can be specified for an //instance// of a
component, or c
an be specified as part of the //implementation// of a component.\n\nIn the
former case, the @Component annotation will be supplemented with a @Mixin
annotation. The @Mixin is a list of one or more mixin classes for that
component.\n\n''Todo: Template syntax for mixins?''\n\nIn the latter case, the
@ComponentClass annotation will be supplemented with a @Mixin
annotation.\n\nMixins can be configured. They can have parameters, just like
ordinary components. When a formal parameter name is ambiguous, it will be
prefixed with the unqualified class name. Thus, you might have to say,
"MyMixin.parameterName=someProperty" if "parameterName" is
ambiguous (by ambiguous, we mean, a parameter of more than one mixin or of the
component itself). \n\nThis disambiguation is //simple//. It is assumed that
the unqualified class name will be sufficient to uniquely identify a mixin.
That is, it is expected that you will not have the same class name even in
different packag
es (as mixins, on a single component). In a //degenerate case// where this is
not so, it will be necessary to disambiguate the mixin name by create a
subclass of the mixin with a new name.\n\n''Todo: how are mixins on a component
implementation configured?''\n\nMixins may have persistent state, just as with
ordinary components.\n\n</div>
<div tiddler="ComponentTemplates" modifier="HowardLewisShip"
modified="200610201807" created="200610201801" tags="">There are some issues
related to component templates.\n\nFirstly, people are really interested in
seeing the return of InvisibleInstrumentation. That is coming.\n\nSecondly,
the idea that templates are well-formed XML documents is causing some
issues.\n\nThe problem is related to entities and doctypes.\n\nUnless you
provide a doctype for the template,
[[entities|http://www.htmlhelp.com/reference/html40/entities/]] don't work;
they result in template parse errors.\n\nIf you provide a standard doctype,
say\n{{{\n <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN"\n
"http://www.w3.org/TR/REC-html40/loose.dtd">\n}}}\n\nYou also get
parse errors, because the DTD does some odd things with comments that the Java
SAX parser doesn't seem to understand.\n\nI've had better luck with the XHTML
doctype:\n{{{\n<!DOCTYPE htm
l PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN"\n"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n}}}\n\nBut
this doesn't render quite the way I want it to.\n\nFurther, entities in the
text are converted to unicode by the parser, then converted to <numeric>
entities on output. Not quite WYSIWYG and potentially confusing.\n\nIt may be
necessary to discard SAX and build a limited XML parser that allows entities to
be passed through unchanged (they would become a special type of document
token).\n\nLastly, the question is how to get the correct DOCTYPE into the
rendered output, espcially in the common case that a Border component provides
the outer tags, as is common in Tapestry 4. This may have to be configured as
a annotation on page classes.</div>
<div tiddler="DeveloperProcedures" modifier="HowardLewisShip"
modified="200610281525" created="200610281524" tags="">Tapestry is a big chunk
of code, growing every day. We need to not step on each other's toes.\n\n//At
this time, Tapestry is pretty single threaded, with Howard setting up the main
infrastructure. Soon there's going to be a crowd of folks working on it, and
we need to coordinate on this ahead of time.//\n\nBasic guidelines:\n\n*
WorkInYourOwnBranch\n* WatchCodeCoverage\n* FocusOnTesting\n*
DontTouchInternals\n</div>
-<div tiddler="DynamicPageState" modifier="HowardLewisShip"
modified="200609211635" created="200609211610" tags="">Tapestry 4 has left
tracking of dynamic page state as an exercise to the developer. Mostly, this
is done using the ''parameters'' parameter of the ~DirectLink
component.\n\nDynamic page state is anything that isn't inside a persistent
page property. For the most part, this includes page properties updated by a
For component\n\nIt seems likely that this information could be automatically
encoded into ~URLs. \n\nI'm envisioning a service that accumulates a series of
//commands//. Each command is used to store a bit of page state. The commands
are serializable. The commands are ultimately serialized into a MIME string
and attached as a query parameter to each URL.\n\nWhen such a link is
triggered, the commands are de-serialized and each executed in turn. Only when
that is finished is any further event processing executed, including calling
into to user code.\n\nM
y outline for this is to store a series of tuples; each tuple is a component
id plus the command to execute.\n\n{{{\npublic interface
ComponentCommand<T>\n{\n void execute(T component);\n}\n}}}\n\nThese
commands should be immutable.\n\nSo a component, such as a For loop component,
could provide itself and a ComponentCommand instance (probably a static inner
class) to some kind of PageStateTracker service.\n\n{{{\npublic interface
PageStateTracker\n{\n void <T> addCommand(T component,
ComponentCommand<T> command);\n}\n}}}\n\nThe commands are kept in the
order that they are added, except that new commands for the same component
//replace// previous commands for that component.\n\nAs with the Tapestry 4 For
component, some mechanism will be needed to store object ids inside the URLs
(that is, inside the commands serialized into URL query parameters) and
translate back to //equivalent// objects when the link is triggered.\n\nDynamic
page state outside of a Fo
rm will overlap with some of the FormProcessing inside the form.</div>
+<div tiddler="DontTouchInternals" modifier="HowardLewisShip"
modified="200611241953" created="200611241953" tags="">Very few of the Tapestry
committers will need to touch anything inside the internals package. When this
occurs, some online discussion may be mandated. The goal of Tapestry is to
ensure that most work for commiters and for developers is in the domain of
creating components, not tinkering with internals.</div>
+<div tiddler="DynamicPageState" modifier="HowardLewisShip"
modified="200611241832" created="200609211610" tags="">Tapestry 4 has left
tracking of dynamic page state as an exercise to the developer. Mostly, this
is done using the ''parameters'' parameter of the ~DirectLink
component.\n\n''Update: As I've thought this one through, I don't think it is
viable outside of Forms. It will end up with long URLs and a constant ambiguity
about whether each link should include or exclude the page state. So this one
is unlikely to get implemented.''\n\nDynamic page state is anything that isn't
inside a persistent page property. For the most part, this includes page
properties updated by a For component\n\nIt seems likely that this information
could be automatically encoded into ~URLs. \n\nI'm envisioning a service that
accumulates a series of //commands//. Each command is used to store a bit of
page state. The commands are serializable. The commands are ultimately
serialized into a M
IME string and attached as a query parameter to each URL.\n\nWhen such a link
is triggered, the commands are de-serialized and each executed in turn. Only
when that is finished is any further event processing executed, including
calling into to user code.\n\nMy outline for this is to store a series of
tuples; each tuple is a component id plus the command to
execute.\n\n{{{\npublic interface ComponentCommand<T>\n{\n void
execute(T component);\n}\n}}}\n\nThese commands should be immutable.\n\nSo a
component, such as a For loop component, could provide itself and a
ComponentCommand instance (probably a static inner class) to some kind of
PageStateTracker service.\n\n{{{\npublic interface PageStateTracker\n{\n void
<T> addCommand(T component, ComponentCommand<T>
command);\n}\n}}}\n\nThe commands are kept in the order that they are added,
except that new commands for the same component //replace// previous commands
for that component.\n\nAs with the Tapestry 4
For component, some mechanism will be needed to store object ids inside the
URLs (that is, inside the commands serialized into URL query parameters) and
translate back to //equivalent// objects when the link is triggered.\n\nDynamic
page state outside of a Form will overlap with some of the FormProcessing
inside the form.</div>
<div tiddler="EditTemplate" modifier="HowardLewisShip" modified="200609210649"
created="200609210648" tags=""><div class='toolbar' macro='toolbar
+saveTiddler -cancelTiddler deleteTiddler'></div>\n<div
class='title' macro='view title'></div>\n<div class='editor'
macro='edit title'></div>\n<div class='editor' macro='edit
text'></div>\n<div class='editor' macro='edit
tags'></div><div class='editorFooter'><span macro='message
views.editor.tagPrompt'></span><span
macro='tagChooser'></span></div></div>
<div tiddler="EnvironmentalServices" modifier="HowardLewisShip"
modified="200609260145" created="200609251547" tags="">Frequently, different
components need to //cooperate// during the rendering process.\n\nThis is an
established pattern from Tapestry 4, which an enclosing component provides
services to the components it encloses. By //encloses// we mean, any components
that are rendered as part of the Form's body; give the use of the
Block/~RenderBlock components, this can not be determined statically, but is
instead determined dynamically, as part of the rendering process.\n\nThe
canoncial example of this pattern is Form component, and the complex
relationship it has with each form element component it encloses.\n\nIn
Tapestry 4, this mechanism was based on the ~IRequestCycle which could store
named attributes. The service providing component would store itself into the
cycle using a well known name, and service consuming components would retrieve
the service using the sam
e well known name.\n\nFor Tapestry 5, this will be formalized. A new service
will be used to manage this information:\n\n{{{\npublic interface
Enviroment\n{\n <T> T push(Class<T> type, T instance);\n\n
<T> peek(Class<T> type);\n\n <T> T pop(Class<T>
type);\n}\n}}}\n\nThe Environment is unique to a request.</div>
+<div tiddler="FocusOnTesting" modifier="HowardLewisShip"
modified="200611241954" created="200611241954" tags="">I'm still not at the
stage of test first, but with every line of code I write, I am thiking about
how I will test that line of code. Tapestry uses EasyMock extensively, and
there's lots of existing code examples to work with.\n\n!Dont Despair\n\nI
occasionally get exhausted by the amount of test code I write for simple chunks
of code. And I inevitably find a broken line of code that would be a major
pain to locate inside a running application, but easy inside a unit test. Keep
your eyes on the big picture.</div>
<div tiddler="FormProcessing" modifier="HowardLewisShip"
modified="200609211540" created="200609210203" tags="forms">Form processing in
Tapestry 4 had certain strengths and limitations.\n\nBasically, any action
framework that can do a simple mapping from query parameters to bean property
names has advantages in terms of simple forms, and Tapestry 4's approach has
huge advantages on more complex forms (with some considerable developer and
framework overhead).\n\nWith a direct mapping of query parameter names to bean
names, each query parameter becomes self describing. You map query parameters
to property of some well known bean. You do simple conversions from strings to
other types (typically, ints and dates and the like). You drop query parameters
that don't match up. You leave a lot of validation and other plumbing (such as
getting those values into your DataTransferObjects) to the developer.\n\nBut
you never see a ~StaleLinkException.\n\nYou also have some unwanted loophol
es in your application in that //any// property can be updated through the
URL. This is //one step// towards a security hole.\n\n!Tapestry 4
Approach\n\nEvery form component, as it renders, asks the Form that encloses it
to provide a client id. The terminology is a little messed; client id is the
unique (within the form) name for //one rendering// of the component. If the
component renders multiple times, because of loops, each rendering gets a
unique name. This becomes the <input>'s name attribute, and ultimately,
the query parameter name.\n\nTapestry attempts to make the client id match the
(user provided) component id. This is not always possible, especially in a
loop, in which case a numeric suffix may be appended to the id to (help) ensure
uniqueness.\n\nOn render, a sequence of //component activations// occur, guided
by the normal render sequence. The exact sequence of activations guides\nthe
production of client ids.\n\nUsing more advanced Tapestry techniques,
including loops, conditionals and the Block/RenderBlock combo, the exact set
of components and\ncomponent activations that will occur for a given rendering
of a given form can not be predicted statically. Tapestry must actually render
out the form\nto discover all of these.\n\nIn fact, while the Form component is
producing this series of client ids, it builds up the list and stores it into
the rendered page as a hidden form field. It will need it later, when the
client-side form is submitted back to the server.\n\nAn advantage of this
approach is the disconnect between the query parameter names (the client ids)
and the objects and properties being editted. Often the client ids will be
//mneumonic// for the properties, but aren't directly mapped to them. Only the
components responsible for each query parameter know how to validate the
submitted value, and what property of which object will need to be
updated.\n\nWhen a form submission occurs, we want to ensure that each quer
y parameter value read out of the request is applied to the correct property
of the correct object. There's a limit to how much Tapestry can help here
(because it has only a casual knowledge of this aspect of the application
structure).\n\nDuring this submission process, which endded up with the curious
name, //rewind phase//, Tapestry must do two things:\n* Activate each
component, such that the component may re-determine its client id, read its
parameter, and update its page property\n* Validate that the process has not
been comprimised by a change of server side state\n\nThat second element is a
tricky one; things can go wonky if a race condition occurs between two users.
For example, lets take a simple invoice and line item model. If users A and B
both read the same invoice, user A adds a line item, and user B changes a line
item ... we can have a problem when user B submits the form. Now that there are
three line items (not two) in the form, there will be extra componen
t activations to process query parameters that don't exist in the request.
\n\nThis scenario can occur whenever the processing of the form submission is
driven by server-side data that can change between request.\n\nTapestry detects
this as a difference in the sequence of client ids allocated, and throws a
~StaleLinkException, which is very frustrating for developers to comprehend and
fix.\n\nThere are also other edge cases for different race conditions where
data is applied to the wrong server-side objects.\n\nThe Tapestry 3 ~ListEdit
component, which evolved into the Tapestry 4 For component, attempts to
address this by serializing a series of //object ids// into the form (as a
series of hidden fields). This requires a bit of work on the part of the
developer to provide an ~IPrimaryKeyConverter that can help convert objects to
ids (when rendering) and ids back to objects (during form
submission).\n\nGenerally speaking, the Tapestry 4 approach represents layers
of kludge o
n layers of kludge. It works, it gets the job done, it can handle some very
complex situations, but it is less than ideal.\n\n!Tapestry 5\n\nThe goal here
is to capture the series of //component activations//, along with any
significant page state changes, during the render.\n\nThese activations will be
a series of //commands//. For each component activation there will be two
commands: the first command will be used to inform the component of its client
id (this command executes during render and during form submission). The second
command will request that the client handle the form submission (this command
executes only during form submission).\n\nThe serialized series of commands is
stored as a hidden form field.\n\nThere's a lot of API to be figured out,
especially the relationship between the form components and the form
itself.\n\nFurther, a lot of what the Tapestry 4 For component does, in terms
of serializing dynamic page state, will need to fold into this as well.
\n\nThe end result will be a single hidden field with a big MIME string inside
it ... but compared to the Tapestry 4 Form component (which has to write out
many hidden fields) the whole will be less than the sum of the parts ... due to
the overhead of serialization and gzip compression.\n\n\n\n\n\n\n</div>
<div tiddler="InvisibleInstrumentation" modifier="HowardLewisShip"
modified="200610201803" created="200610201802" tags="">A feature of Tapestry 4
where the component id, type and parameters were "hidden" inside
ordinary HTML tags.\n\nThis will show up inside Tapestry 5 pretty soon, and
look something like:\n{{{\n<span t:type="If"
t:test="prop:showWarning" class="warning"> \n . .
.\n</span>\n}}}</div>
<div tiddler="LogicalPageName" modifier="HowardLewisShip"
modified="200610081330" created="200610081330" tags="">A logical page name is
the name of a page as it is represented in a URI.\n\nInternally, Tapestry
operates on pages using full qualified class names. Technically, the FQCN is
the class of the page's root element, but from an end developer point of view,
the root element is the page.\n\nThe logical page name must be converted to a
fully qualified class name.\n\nA set of LibraryMappings are used. Each library
mapping is used to express a folder name, such as "core", with a Java
package name, such as org.apache.tapestry.corelib. For pages, the page name is
searched for in the pages sub-package (i.e.,
org.apache.tapestry.corelib.pages). Component libraries have unique folder
names mapped to root packages that contain the pages (and components, and
mixins) of that library.\n\nWhen there is no folder name, the page is expected
to be part of the application,
under the pages sub-package of the application's root package.\n\nIf not found
there, as a special case, the name is treated as if it were prefixed with
"core/". This allows access to the core pages (and more importantly,
components -- the search algorithm is the same).\n\nFinally, pages may be
organized into folders. These folders become further sub-packages. Thus as
page name of "admin/EditUsers" may be resolved to class
org.example.myapp.pages.admin.EditUsers.\n\n</div>
<div tiddler="MainMenu" modifier="HowardLewisShip" modified="200609210701"
created="200609210643" tags="">MasterIndex\n[[RSS
feed|tap5devwiki.xml]]\n\n[[Tapestry 5
Home|http://tapestry.apache.org/tapestry5/]]\n[[Howard's
Blog|http://howardlewisship.com/blog/]]\n\n[[Formatting
Help|http://www.blogjones.com/TiddlyWikiTutorial.html#EasyToEdit%20Welcome%20NewFeatures%20WhereToFindHelp]]</div>
-<div tiddler="MasterIndex" modifier="HowardLewisShip" modified="200611040038"
created="200609202214" tags="">Top level concepts within Tapestry 5.\n\nA
//meta-note//: This is where new ideas are first explained, usually before
being implemented. In many cases, the final implementation is\nnot a perfect
match for the notes. That's OK ... as long as the official Maven documentation
does a good job. It's not reasonable to expect developers to jump back in here
and dot every i and cross every t if they're already expected to generate good
Maven documentation.\n\n* PropBinding -- Notes on the workhorse
"prop:" binding prefix\n* TypeCoercion -- How Tapestry 5 extensibly
addresses type conversion\n* FormProcessing\n* DynamicPageState -- tracking
changes to page state during the render\n* EnvironmentalServices -- how
components cooperate during page render\n* ComponentMixins -- A new fundamental
way to build web functionality\n* RequestTypes -- Requests, request processing
, URL formats\n* ComponentTemplates -- Issues about Component Templates\n*
DeveloperProcedures -- Your a Tapestry committer ... how do you makes
changes?\n* SmartDefaults -- do even more with event less\n* RandomIdeas --
stuff that doesn't fit elsewhere</div>
+<div tiddler="MasterIndex" modifier="HowardLewisShip" modified="200611241944"
created="200609202214" tags="">Top level concepts within Tapestry 5.\n\nA
//meta-note//: This is where new ideas are first explained, usually before
being implemented. In many cases, the final implementation is\nnot a perfect
match for the notes. That's OK ... as long as the official Maven documentation
does a good job. It's not reasonable to expect developers to jump back in here
and dot every i and cross every t if they're already expected to generate good
Maven documentation.\n\n* PropBinding -- Notes on the workhorse
"prop:" binding prefix\n* TypeCoercion -- How Tapestry 5 extensibly
addresses type conversion\n* FormProcessing\n* DynamicPageState -- tracking
changes to page state during the render\n* EnvironmentalServices -- how
components cooperate during page render\n* ComponentMixins -- A new fundamental
way to build web functionality\n* RequestTypes -- Requests, request processing
, URL formats\n* ComponentTemplates -- Issues about Component Templates\n*
DeveloperProcedures -- Your a Tapestry committer ... how do you makes
changes?\n* SmartDefaults -- do even more with event less\n* RandomIdeas --
stuff that doesn't fit elsewhere\n* ProblemsNeedingSolutions\n*
ComponentDocumentation -- Generating Documentation about Components\n*
TapestryLookAndFeel -- Default CSS\n* [[Assets]]\n\n</div>
<div tiddler="OGNL" modifier="HowardLewisShip" modified="200610071249"
created="200609202254" tags="">The [[Object Graph Navigation
Library|http://ognl.org]] was an essential part of Tapestry 4.\n\nOGNL is both
exceptionally powerful (especially the higher order things it can do, such as
list selections and projections). However, for the highest\nend sites, it is
also a performance problem, both because of its heavy use of reflection, and
because it uses a lot of code inside synchronized blocks.\n\nIt will be
optional in Tapestry 5. I believe it will not be part of the tapestry-core, but
may be packaged as tapestry-ognl.\n\nThe "prop:" binding prefix is an
effective replacement for OGNL in Tapestry 5. See PropBinding.\n</div>
<div tiddler="PageRenderRequest" modifier="HowardLewisShip"
modified="200610081333" created="200610071313" tags="">Page render requests are
requests used to render a specific page. //render// is the term meaning to
compose the HTML response to be sent to the client. Note: HTML is used here
only as the most common case, other markups are entirely possible.\n\nIn many
cases, pages are stand-alone. No extra information in the URL is necesarry to
render them. PersistentProperties of the page will factor in to the rendering
of the page.\n\nIn specific cases, a page needs to render within a particular
context. The most common example of this is a page that is used to present a
specific instance of a database persistent entity. In such a case, the page
must be combined with additional data, in the URL, to identify the specific
entity to access and render.\n\n! URI
Format\n\n{{{\n/page-name.html/id\n}}}\n\nHere "page-name" is the
LogicalPageName for the page. \n\nThe &q
uot;.html" file extension is used as a delimiter between the page name
portion of the URI, and the context portion of the URI. This is necessary
because it is not possible (given the plethora of libraries and folders) to
determine how many slashes will appear in the URI.\n\nThe context consists of
one ore more ids (though a single id is the normal case). The id is used to
identify the specific data to be displayed. Further, a page may require
multiple ids, which will separated with slashes. Example:
/admin/DisplayDetail.html/loginfailures/2006\n\nNote that these context values,
the ids, are simply //strings//. Tapestry 4 had a mechanism, the DataSqueezer,
that would encode the type of object with its value, as a single string, and
convert it back. While seemingly desirable, this facility was easy to abuse,
resulting in long and extremely ugly URIs.\n\nAny further information needed by
Tapestry will be added to the URI as query parameters. This may include things
like us
er locale, persistent page properties, applicaition flow identifiers, or
anything else we come up with.\n\n! Request Processing\n\nOnce the page and id
parameters are identified, the corresponding page will be loaded.\n\nTapestry
will fire two events before rendering the page.\n\nThe first event is of type
"setupPageRender". This allows the page to process the context (the
set of ids). This typically involves reading objects from an external
persistent store (a database)\nand storing those objects into transient page
properties, in expectaion of the render.\n\nThe @SetupPageRender annotation
marks a method to be invoked when this event is triggered. The method may take
one or more strings, or an array of strings, as parameters; these will be\nthe
context values. The method will normally return void. Other values are
''TBD''. It may also take other simple types, which will be coerced from the
string [EMAIL PROTECTED] setup(long id)\n{\n . .
.\n}\n}}}\n\n\n\nThe second event is of type "pageValidate". It
allows the page to decide whether the page is valid for rendering at this time.
This most often involves a check to see if the user is logged into the
application, and has the necessary privileges to display the contents of the
page. User identity and privileges are //not// concepts built into Tapestry,
but are fundamental to the majority of Tapestry applications.</div>
+<div tiddler="ProblemsNeedingSolutions" modifier="HowardLewisShip"
modified="200611230401" created="200611230401" tags="">There are a few things
that I'm concerned about.\n\n!Render Complexity\n\nAll those states in the
render component state machine may be a little much, especially
~PreBeginRender, ~BeginRender and ~PostBeginRender. In addition, it doesn't
work for a case I'm interested in ... for link components, I'd like to use the
RenderInformals mixin, but also support a disable parameter that turns off the
<a> tag (but still renders the body). The state machine currently is set
up so that returning false in any of the ~BeginRender states skips all the way
to ~AfterRender, bypassing the template and/or body.</div>
<div tiddler="PropBinding" modifier="HowardLewisShip" modified="200610201450"
created="200609202203" tags="bindings">The "prop:" binding prefix is
the default in a lot of cases, i.e., in any Java code (annotations).\n\nThis
binding prefix supports several common idioms even though they are not,
precisely, the names of properties. In many cases, this will save developers
the bother of using a "literal:" prefix.\n\nThe goal of the
"prop:" prefix is to be highly efficient and useful in 90%+ of the
cases. [[OGNL]], or synthetic properties in the component class, will pick up
the remaining cases.\n\n!Numeric literals\n\nSimple numeric literals should be
parsed into read-only, invariant
bindings.\n{{{\nprop:5\n\nprop:-22.7\n}}}\n\nThe resulting objects will be of
type Long or type Double. TypeCoercion will ensure that component parameters
get values (say, int or float) of the correct type.\n\n!Range
literals\n\nExpresses a range of integer values,
either ascending or descending.\n{{{\nprop:1..10\n\nprop:100..-100\n}}}\n\nThe
value of such a binding is Iterable; it can be used by the Loop
component.\n\n!Boolean literals\n\n"true" and "false"
should also be converted to invariant
bindings.\n{{{\nprop:true\n\nprop:false\n}}}\n\n!String literals\n\n//Simple//
string literals, enclosed in single quotes. Example:\n{{{\nprop:'Hello
World'\n}}}\n\n//Remember that the binding expression will always be enclosed
in double quotes.//\n\n!This literal\n\nIn some cases, it is useful to be able
to identify the current component:\n{{{\nprop:this\n}}}\n\nEven though a
component is not immutable, the value of //this// does not ever change,\nand
this binding is also invariant.\n\n!Null literal\n\n{{{\nprop:null\n}}}\n\nThis
value is always exactly null. This can be used to set a parameter who'se
default value is non-null to the explicit value null.\n\n!Property
paths\n\nMulti-step property paths are extremely importa
nt.\n\n{{{\nprop:poll.title\n\nprop:identity.user.name\n}}}\n\nThe initial
terms need to be readable, they are never updated. Only the final property name
must be read/write, and in fact, it is valid to be read-only or
write-only.\n\nThe prop: binding factory builds a Java expression to read and
update properties. It does not use reflection at runtime. Therefore, the
properties of the //declared// type are used. By contrast, [[OGNL]] uses the
//actual// type, which is reflection-intensive. Also, unlike OGNL, errors (such
as missing properties in the property path) are identified when the page is
loaded, rather than when the expression is evaluated.\n</div>
-<div tiddler="RandomIdeas" modifier="HowardLewisShip" modified="200611041749"
created="200611040039" tags="">!HTML / XHTML DTDs\n\nThe template parser should
include local (in JAR) copies of the HTML and XHTML DTDs and redirect the
parser to use the local copies. This can be a huge performance boost when
parsing a template.\n\n!final should imply @Retain\n\nFinal fields should be
treated as if they have the @Retain annotation\n\n! Exceptions from event
handler / phase render methods\n\nTapestry should wrap non-runtime exceptions
from these methods. I think today, if you declare that such a method throws an
exception, you'll get a runtime exception out of Javassist.\n\n!
SubForms\n\nPerhaps one way to approach highly dynamic, Ajax pages with forms
is to have a logical "sub form" concept. A sub form would work inside
an existing form, and organize a group of fields within that form. Processing
of the fields would occur only if the sub form was active, which itself\nw
ould be tracked based on visibility of the sub form (a sub form in an
invisible panel would not be processed on the server side). This idea needs a
lot of fleshing out, even to see if it is viable.\n\n! Ajax Constraints\n\nThe
best way to tackle Ajax features, especially w.r.t. forms, is to put some
sensible constraints on what the user can do, then make it easy to implement
those things.\n\nBasically ... never delete! Deletions are a real pain to
handle, unless I suddenly get much smarter. Allow things to be hidden on the
client side,\nand for the corresponding fields to do nothing on the server
side, but don't allow them to full out delete. \n\nAllow new things to be
added, preferable only at the "tail end" of the form. </div>
+<div tiddler="RandomIdeas" modifier="HowardLewisShip" modified="200611230501"
created="200611040039" tags="">!HTML / XHTML DTDs\n\nThe template parser should
include local (in JAR) copies of the HTML and XHTML DTDs and redirect the
parser to use the local copies. This can be a huge performance boost when
parsing a template.\n\n!final should imply @Retain\n\nFinal fields should be
treated as if they have the @Retain annotation\n\n! Exceptions from event
handler / phase render methods\n\nTapestry should wrap non-runtime exceptions
from these methods. I think today, if you declare that such a method throws an
exception, you'll get a runtime exception out of Javassist.\n\n!
SubForms\n\nPerhaps one way to approach highly dynamic, Ajax pages with forms
is to have a logical "sub form" concept. A sub form would work inside
an existing form, and organize a group of fields within that form. Processing
of the fields would occur only if the sub form was active, which itself\nw
ould be tracked based on visibility of the sub form (a sub form in an
invisible panel would not be processed on the server side). This idea needs a
lot of fleshing out, even to see if it is viable.\n\n! Ajax Constraints\n\nThe
best way to tackle Ajax features, especially w.r.t. forms, is to put some
sensible constraints on what the user can do, then make it easy to implement
those things.\n\nBasically ... never delete! Deletions are a real pain to
handle, unless I suddenly get much smarter. Allow things to be hidden on the
client side,\nand for the corresponding fields to do nothing on the server
side, but don't allow them to full out delete. \n\nAllow new things to be
added, preferable only at the "tail end" of the form. \n\n! SPI
Package\n\nA number of interfaces, such as Binding, probably belong in a SPI
(Service Provider Interface) package, since they will generally only be used by
authors of Tapestry extensions. Perhaps we should just use the oat.services
package as the SPI package?</div>
<div tiddler="RequestTypes" modifier="HowardLewisShip" modified="200610081334"
created="200610071243" tags="request">There are three broad categories of user
requests (requests from the client web browser):\n\n* PageRenderRequest --
requests to render a specific page, possibly with some configuration\n*
ComponentActionRequest -- requests that trigger behaivor within a specific
component\n* ResourceRequest -- requests that access a resource file within the
classpath\n\nEach of these requests has a specific URI format.</div>
<div tiddler="SideBarTabs" modifier="HowardLewisShip" modified="200609210652"
created="200609210651" tags=""><<tabs txtMainTab Timeline Timeline
TabTimeline All 'All tiddlers' TabAll Tags 'All tags' TabTags More 'More lists'
TabMore>>\n</div>
<div tiddler="SiteSubtitle" modifier="HowardLewisShip" modified="200609202249"
created="200609202155" tags="">\nThe quick and dirty one-stop shopping of
random ideas for Tapestry 5.</div>
<div tiddler="SiteTitle" modifier="HowardLewisShip" modified="200609202249"
created="200609202155" tags="">Tapestry 5 Brain Dump</div>
<div tiddler="SiteUrl" modifier="HowardLewisShip" modified="200609210703"
created="200609210641"
tags="">http://tapestry.apache.org/tapestry5/tap5devwiki.html</div>
-<div tiddler="SmartDefaults" modifier="HowardLewisShip"
modified="200611032359" created="200611032344" tags="">As great as the
annotations are, allowing things to work without the annotations could be even
better.\n\n!Event handler methods\n\nMethods with the prefix "on"
could automatically be considered event handler methods. The string after the
prefix, converted to lower case, would be the event type. We could even add
"from//~ComponentId//" to the end. Examples (with annotation
equivalents):\n\n* onSubmit --> @~OnEvent("submit")\n*
onSubmitFromForm --> @~OnEvent(value="submit",
component="form")\n* onUpdateFromSelect -->
@~OnEvent(value="update", component="select")\n\n!Render
phase methods\n\nNaming a method the same as the render phase (with the first
character lower case). Again, Tapestry could deduce the phase from the method
name, as if the annotation were present:\n\n* beforeRender
() --> @~BeforeRender\n* beforeRenderBody() -->
@~BeforeRenderBody\n\netc. Again, the methods don't have to be public, they
just have to have the correct name. In every other way they are the same as
annotated render phase methods except that they don't have the
annotation.\n\nThere may be some minor implications w.r.t. render phase method
ordering.\n\netc.\n\n!Other Ideas\n\nThis gets more component specific. I had
the idea that a ~TextField whose id was "userId" might want to edit a
property named "userId" as the default for when its value parameter
is unbound. I think to accomplish this, we need the concept of computed
bindings for unbound parameters ... perhaps in the form of methods that return
a Binding with a name and/or annotation, for example:\n\n{{{\n\n @Inject\n
private ComponentResources _resources;\n\n
@Inject("infrastructure:bindingSource")\n private BindingSource
_source;\n\n @Parameter\n private Object _value;\n\n B
inding valueDefault()\n {\n ComponentResources containerResources =
_resources.getContainer().getComponentResources();\n return
_source.newBinding("default value", containerResources,
"prop", _resources.getId(), null); \n }\n}}}\n\nSo valueDefault()
is invoked if the value parameter is not bound. The component uses its own
immediate id ("userId") as the name of a property of its container
(typically, the page). ~ComponentResources does not yet implement
getContainer(), but the rest would work.\n\nIf this was widespread, there could
be even better optimizations for it. Perhaps container resources could just be
passed into the method as a parameter, to save the code to find it. Ditto with
BindingSource. Once again, rather than come up with complex XML-ese to come up
with defaults, we're trying to work //with// Java code.\n</div>
+<div tiddler="SmartDefaults" modifier="HowardLewisShip"
modified="200611231535" created="200611032344" tags="">As great as the
annotations are, allowing things to work without the annotations could be even
better.\n\n!Event handler methods\n\nMethods with the prefix "on"
could automatically be considered event handler methods. The string after the
prefix, converted to lower case, would be the event type. We could even add
"from//~ComponentId//" to the end. Examples (with annotation
equivalents):\n\n* onSubmit --> @~OnEvent("submit")\n*
onSubmitFromForm --> @~OnEvent(value="submit",
component="form")\n* onUpdateFromSelect -->
@~OnEvent(value="update", component="select")\n\n!Render
phase methods\n\nNaming a method the same as the render phase (with the first
character lower case). Again, Tapestry could deduce the phase from the method
name, as if the annotation were present:\n\n* beforeRender
() --> @~BeforeRender\n* beforeRenderBody() -->
@~BeforeRenderBody\n\netc. Again, the methods don't have to be public, they
just have to have the correct name. In every other way they are the same as
annotated render phase methods except that they don't have the
annotation.\n\nThere may be some minor implications w.r.t. render phase method
ordering.\n\netc.\n\n!Other Ideas\n\nThis gets more component specific. I had
the idea that a ~TextField whose id was "userId" might want to edit a
property named "userId" as the default for when its value parameter
is unbound. I think to accomplish this, we need the concept of computed
bindings for unbound parameters ... perhaps in the form of methods that return
a Binding with a name and/or annotation, for example:\n\n{{{\n\n @Inject\n
private ComponentResources _resources;\n\n
@Inject("infrastructure:bindingSource")\n private BindingSource
_source;\n\n @Parameter\n private Object _value;\n\n B
inding valueDefault()\n {\n ComponentResources containerResources =
_resources.getContainer().getComponentResources();\n return
_source.newBinding("default value", containerResources,
"prop", _resources.getId(), null); \n }\n}}}\n\nSo valueDefault()
is invoked if the value parameter is not bound. The component uses its own
immediate id ("userId") as the name of a property of its container
(typically, the page). ~ComponentResources does not yet implement
getContainer(), but the rest would work.\n\nIf this was widespread, there could
be even better optimizations for it. Perhaps container resources could just be
passed into the method as a parameter, to save the code to find it. Ditto with
BindingSource. Once again, rather than come up with complex XML-ese to come up
with defaults, we're trying to work //with// Java code.\n</div>
<div tiddler="TabAll" modifier="HowardLewisShip" modified="200609210650"
created="200609210650" tags=""><<list all>></div>
+<div tiddler="TapestryLookAndFeel" modifier="HowardLewisShip"
modified="200611241943" created="200611241943" tags="">I think it would be very
compelling to create a reasonably sharp default CSS for Tapestry applications.
Something pretty, standards based, Web-2.0-ish, that would make even the
simplest apps stand out.\n\nOf course, the Tapestry default CSS would be the
first stylesheet, and could be overridden by additional stylesheets or inline
styles.</div>
<div tiddler="TypeCoercion" modifier="HowardLewisShip" modified="200610051240"
created="200609202217" tags="parameters types">Automatic coercion of types is
essential. This primarily applies to component parameters.\n\nParameters are
tied to the
[[Binding|http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/Binding.html]]
interface.\n\nTapestry component parameters look like simple instance
variables, but Tapestry's RuntimeTransformation of component classes means that
reading the value of a parameter instance variable //may// invoke
Binding.get(), and changing the value of a parameter instance variable will
invoke Binding.set().\n\n!Reading From Parameters\n\nReading a parameter value
involves two steps:\n* Invoking Binding.get()\n* Converting the result to the
type of the parameter (where different)\n\nWhen reading parameters, the binding
will provide an object of the type of the bound property. Various kinds of
invariant bindings will returned a fixed type,
typically a String.\n\nThe parameter will be assigned to a variable that has a
known type, possibly a primtive type (int, boolean) or an object type (Map,
Date).\n\n!Writing To Parameters\n\nWriting to, or updating, a parameter is in
two steps:\n* Converting the new value into a type appropriate for the
binding\n* Invoking Binding.set()\n\nWe will be adding a getPropertyType()
method to the Binding interface, that will identify the property type of the
property bound to the parameter.\n\nThe component will be responsible for
performing a coercion from the value provided to the proper type, before
invoking Binding.set().\n\n!Coercion Tuples\n\nAt the core of this will be a
service that performs type coercions.\n\nCoercions are based on //coercion
tuples// that define:\n* A source type\n* A target type\n* An object to perform
the coercion from source to target\n* A "cost" for the conversion
(possibly, but usually with a standard default value) ''(not yet implemented)
''\n\nAs a special case, the type of null will be treated as type void (i.e.,
void.class). Thus we can use the same mechanism to identify how to convert
from null to other types, such as Boolean or Integer.\n\nThere should be a
large number of these tuples available. The most common tuples may be
conversions between various types and String.\n\n!Coercion Algorithm\n*
Determine the source type (treating null as void.class)\n* Determine the target
type (converting primitive types to equivalent wrapper types)\n* If the source
type is assignable to the target type, then the input value is valid and the
process is complete\n* Find a converter that converts between the source type
and the target type, pass the source value through the converter to get a
target value\n\nThat last part needs a bit of expansion.\n\nFirst off, there
will often ''not'' be a tuple for coercing directly form the source type to the
target type.\n\nIn that scenario, the conversion will involve a search
to find a sequence of tuples that will perform the coercion. This will take
the form a breadth-first search where we look for tuples that coerce from the
source type to an intermediate type, then search for tuples from the
intermediate type to the target type. This may involve more than two
coercions.\n\nYou can think of the set of tuples as a kind of directed graph.
Each type is a node on the graph, and each tuple represents a connection
between one type and another type (say, from String to Double). What we're
trying to do is find a path form a source type (or some super-class or
super-interface of the source type) to some target type (or sub-class or
sub-interface of the target type).\n\nMay need to express a "cost" of
the coercion from start type to target type; this might be useful if there are
multiple paths for the conversion. Cost may factor in both the computing
expense, and any loss of detail. Basic cost is established in terms of the
number of steps
and enforced by the order in which tuples are considered and combined.\n\nFor
example, a coercion tuple from Number to Float may be represented as the
tuple:\n(Number, Float, {{{ return new Float(input.floatValue());
}}})\n\n{{{\npublic interface Coercion<S,T>\n{\n T coerce(S
input);\n}\n}}}\n\nIf the input type is an Integer, then a search for
Integer->Float will find no entries. At that point, it will be necessary to
"climb" the inheritance tree and look for coercions from Number (the
super class of Integer); this will find the Number->Float tuple.\n\nAgain,
in terms of cost, we might also find a pair of tuples: Object->String and
String->Float. This will have a higher cost than the Number->Float tuple
and should be rejected in favor of the lower cost coercion.\n\n//Note: cost
hasn't been implemented, and likely won't be, unless and until the algorithm as
it stands is shown to provide less than optimal results.//\n\nThe algorithm
caches t
he result of this search, with proper guards for concurrent access. The cache
is cleared when an invalidation of the component class loader
occurs.\n\n!Configuring the service\n\nThis has been implemented as service
tapestry.TypeCoercer.\n\nThe configuration of this service is an unordered
collection of CoercionTuple.</div>
+<div tiddler="WatchCodeCoverage" modifier="HowardLewisShip"
modified="200611241957" created="200611241956" tags="">The code coverage tools
built into ''mvn site'' are quite useful. Right now, overall coverage is at
93%. Keep an eye on code coverage, including branch coverage (do you test both
outcomes of an if statement?). Use unexecuted code to target your
efforts.\n\nI often do a cursory unit test for "normal behavior",
plus more exaustive unit tests for error conditions. I then "back the
test up" using an integration test (build with [[Selenium]]) to prove that
the normal behavior case really works.</div>
<div tiddler="WorkInYourOwnBranch" modifier="HowardLewisShip"
modified="200610281536" created="200610281528" tags="">Working in the trunk can
be a problem. ''The SVN trunk is where merges happen, not where development
happens.''\n\nFor any bit of code change you make, you want to do the
following:\n\n* Branch trunk to form your own sandbox\n* Work in the sandbox\n*
Ensure high quality: high code coverage, unit and integration tests, up-to-date
documentation\n* Announce (on the developer mailing list) that you are
committing to trunk\n* Switch your workspace back to trunk\n* Tag trunk as
premerge\n* Merge from your sandbox\n* Ensure a good merge (including
documentation, tests, and code coverage)\n* Commit your merge to trunk\n* Tag
trunk as postmerge\n\n!Branch names\n\nBranch names should consist of your user
id, the current date as YYYYMMDD, and a short mneumonic, such as a bug id.
Example: {{{hlship-20061027-removeaspectj}}}.\n\nThere's a branches folder for
tapestry5/t
apestry-core, i.e.
[http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/branches/]\n\n!Tag
names\n\nPrefix the branch name with "premerge" or
"postmerge". i.e.
[http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/tags/]\n\nThese
are really important when trying to back out a change, the pre and the post
give a lot of context to see what actually changed.\n\n!Announcing\n\nMerging
is hard enough, it's worse if two people are making possibly conflicting
changes at the same time. A little coordination goes a long way.\n\n!Small
increments are ''Good''\n\nThis looks like a lot of overhead, but thanks to
Subversion, it really isn't. It's still better to do small increments of work.
Don't go away for six months and expect an easy job of committing changes. You
can do this style of work several times a day (Subversion was created
specifically to make tagging, branching, and merging fast).</div>
</div>
<!--POST-BODY-START-->