Make struts2 more component-friendly
------------------------------------
Key: WW-2826
URL: https://issues.apache.org/struts/browse/WW-2826
Project: Struts 2
Issue Type: New Feature
Affects Versions: 2.1.2
Reporter: Sami Dalouche
Contrary to component-oriented frameworks , Struts2 architecture does not try
to ignore what the web is, it just tries to make web programming easier without
introducing complex component lifecycles.
However, there are a few things that the component-oriented frameworks got
right, and I believe struts2 should build on its architecture to enable users
to create UI components easily.
These needs arise from the project I am currently working on, and I describe
the hacks that I currently do to 'simulate' components. I also try to envision
the features that struts2 could add to make writing components easier. This
'request' should be seen as a meta-feature request that can be used as a basis
for discussing what should be included. Once everybody agrees, we can create
smaller issues that target individual concerns.
So, let's define what a reusable UI component needs to work decently:
- Markup template. (the view)
- Custom CSS for the component
- Custom JavaScript for the component
- Access to helper Java code
- Access to a given model, expressed as a Java class that has to be pushed on
the value stack, so that it can catch URL/form parameters related to the
component
- When the component displays part of a form, the action has to delegate part
of the validation to the component's model, using @VisitorFieldValidator
- a Custom interceptor to set parameters on the action
- Custom properties file for I18N
Frameworks such as tapestry have built-in features for all these concerns. Here
is the struts2-way to achieve the same, with the current feature set :
- Markup/View: Markup template expressed as FTL, either using the Struts2
Component / Taglib infrastructure, or just plain FTL files that have nothing to
do with struts2
- Custom CSS and JavaScript either sourced or included in a specific
@component.head tag. the developer is responsible for including head in the
head section of the page. The head macro has to be coded in a way that does
not include JS / CSS directly, but first checks if it hasn't already be
included, in case several components transitively depend on a given component
and call it's head as part of the head tag/macro. Also, the tag is responsible
to use either the real .css/.js file, or the one generated by the build system
in case there are hooks to merge CSS and JS files in order to reduce the number
of requests.
- Helper java functions have to be registered using a Custom Freemarker
Manager. Adding helpers to the freemarker context is pretty straightforward,
but adding the services to the ValueStack too is a little tricky. You have to
make sure you don't push the same service twice in the same request, because
populateContext() is called several times...
- The component's java model has to be pushed by the action. pretty much like
the action's view has to reference the head tag in the head section. I use a
MultipleModelDrivenInterceptor that works like ModelDrivenInterceptor, except
that is pushes a list of objects, instead of just one. In case the component
adds fields to the form, the action is responsible for adding
@VisitorFieldValidator.
- For important parameters and session attributes that are considered globally
accessible on the website, specific interceptors + *Aware interfaces can be
used to ease the actions' job. Things like Users, etc, can be directly injected
to the action to avoid boilerplate copy/pasted code.
- Each component has its own properties file, that is registered in struts.xml
as a global resource bundle.
OK, now, why I think all this stuff is suboptimal :
- There is no common concept in struts that defines what a component is. so all
the pieces are available, but it looks like tricks and hacks that we try to get
to work. It would definitely be less worrying for the developer if struts
provided an 'official' way to do it.
- the developer is responsible for adding head to the HTML head section, for
creating the component's Java Model, for pushing it on the value stack, and for
delegating validation to the component. WAO !!! In most cases, if you're not
the one who developed the component, you will forget one of these steps.
- The developer is responsible for making sure keys are globally unique among
components... Hum... Once again, this gives the impression of a mere hack...
Pushing some resource bundle using the I18N tag is not an option since it
renders the use of <#nested> useless. For instance, if a component did :
<@s.i18n name="myComponentResourceBundle>
<@s.text name="whatever.key.in.component.resource.bundle" />
<#nested />
</@s.i18n>
then if the component is called with <@s.text /> code in the <#nested />
section, the <@s.text would not find its key in the component resource bundle,
and would then fail. Implementing a fallback mechanism that would go down the
value stack could partly work, but would still not fix the duplicate key
problem.
- When we want to do some processing depending on the parameters, and inject
some variables to the current action thanks to a custom interceptor, we don't
have access to type converters, we basically need to do the job by hand (or
probably instantiate the type converters, or do whatever hardcore low level
struts or OGNL stuff to achieve the same). So, the only real way to bind
parameters is to use a custom model class that is instantiated and pushed on
the value stack by the action, but then the action has too much stuff to do
regarding the component. See previous section regarding custom java component
models.
So, what I think struts could add in order to make writing components more
consistent and easier :
1] A small wiki section that describes the 'preferred' struts approach to
creating reusable components, whatever approach is chosen. Even if the wiki
section is considered as a mere suggestion, it would allow users to realize
that it is in fact possible to write components using struts2, and would
certainly help struts2 beginners.
2] A kind of generic behavior that would allow components (Struts 2 Tag
Components) to register custom CSS and JS code, that would be printed in the
default struts2 themes. (<@s.head />). In order to select which components are
used on a page, I guess that parsing the FTL would be too weak ? So, it would
be acceptable to force the action to declare the list of the components that
are used. Do some people have an idea regarding this ?
3] A way for Struts2 components to declare :
- A method to create the associated java model object, for every request where
the component is used. This model object should be pushed to the value stack
automatically.
- the ModelDriven-validation.xml rules that should be included when the
component is used. The main problem I see with that is that is to allow
switching validation off, because the action would probably want to validate
the model for the execute() method, but not the input() one.
- An interface that an action could implement to receive the populated model
(MyModelAware.class), as well as some callback method that is used to set the
model on the implementing action. A generic interceptor could then use whatever
ThreadLocal/request-scoped object to retrieve all the callback methods that
have to be called on the current action, depending on the types implemented by
the action.
- The name of a spring/whatever IoC service that should be registered in the
freemarker + OGNL context, so that FTL views can refer to it
- The name of a .properties file to use for I18N keys lookup. To make this to
work, my guess is that @s.text would need to be completly reworked to be
component aware, since both global keys, and trying eveyr textprovider in the
value stack both don't fix the duplicate key problems. Maybe some experts have
a better idea to solve this issue ?
That's it ! (I think) :p
So yeah, still a lot of questions, it's definitely a tough problem, but I
really believe it's worth thinking a little bit about that.
In my project, I am progressively starting to extract abstractions to create
components, so don't hesitate to bring your ideas to complement what I
suggested. I would happily share the code that implements these ideas whenever
it is available.
Regards,
Sami Dalouche
--
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.