Details
-
Improvement
-
Status: Closed
-
Major
-
Resolution: Won't Fix
-
5.0.11, 5.0.12
-
None
-
None
Description
It does not appear to be possible to specify the t:id value through more than one layer of a component hierarchy. This becomes a problem when trying to build reusable form widgets, particularly as the validation infrastructure uses the component's t:id as the key into configuration (via properties), e.g. for regexp.
Here is an example of what I'm trying to do – the motivation being to build a set of reusable form building blocks for a large number of corporate developers (and many separate systems), the goal being to encapsulate markup/CSS standards into a library... effectively a simple DSL.
Imagine a set of "Question" widgets, e.g. a QuestionText that has something like the following TML:
<div>
<t:label class="question" for="textQuestion" />
<t:textfield t:id="textQuestion" ... />
<t:if test="required"> <em>*</em> </t:if>
</div>
Using this widget we can write simply:
<div t:type="QuestionText" t:id="accreditationCode" validate="required,regexp" />
rather than the full set of markup for each question, and we've encapsulated non-semantic markup such as the wrapping div, the order of label and field, the presence of a mandatory field marker and css classes. Component frameworks are ideal...
BUT... because we have to set the t:id="textQuestion" in this QuestionText component (e.g. for label to work, and because t:id cannot be bound to a parameter/expression), when we drill into using Tapestry's sophisticated features, life becomes hard.
We can reproduce the automatic "value" -> "id" transformation that Tapestry does for TextField by reproducing AbstractTextField.defaultValue() in our QuestionText component. Similarly, we can pass through validators by injecting FieldValidatorSource. It's a little painful, but worth the effort (for my use case). Using clientId is necessary also, to build nice, testable (e.g. WebDriver) markup – although perhaps that will break in larger/iterated composites.
But since FIeldValidatorSourceImpl uses ComponentResources.getId() to lookup constraints and messages in the Messages "context", it does not appear possible (at least to me) to easily override this behaviour.
Being able to programmatically override t:id might break the static structure / dynamic behaviour model, but I'm wondering whether t:id is doing double-duty here, and perhaps there needs to be another abstraction for id (~ controlName, ~clientId) that <em>can</em> be injected across multiple layers of components – perhaps defaulting to t:id otherwise... but that would seem to need another entity such as t:logicalId, with all the required changes back up to the template parser...
AFAIK, Field.controlName or ClientElement.clientId are a little too "generated" for uniqueness reasons.
I'm not sure if mixins can be used here, but I'd rather use the existing Tapestry validation framework than implement a parallel one. Perhaps a less obvious approach, but maybe more elegant, is to look at decorating standard Field components.
In summary... does t:id have to be overridden to build up reusable (larger) form widgets, or are there some more feasible alternatives?