I agree with most of these statements, but I would start even higher
than that, like defining the base interfaces and access classes.
For instance, I think it's safe to assume that a Skin class or
interface should exists. Let assume all interfaces for now as it takes
less characters. Now, from where and how should the Skin be available
for a given FacesContext. Personally I believe a Skin should be linked
to a RenderKit (or a common base RenderKit class for all MyFaces
extensions if not in the standard), but let assume work on the API for
now for the sake of the discussion.
So for now we have:
public interface Skin
{
public String getResourceBundle();
}
public abstract class RenderKit
{
public abstract Skin getSkin(FacesContext context);
}
So far so good, but now that opens some issues that will themselves
explode into more:
1. Should the way the RenderKit locates the current Skin standardized?
2. How do we define the contract ensuring that the Skin get
rendered on the page?
3. What other functionalities should be standarized in the Skin
interface?
*Issue 1: Current skin localization*
Personally I chose yes to that question since it allow a standardized
way to define skins. Let use Trinidad's skin-family notion here, but
with the way renderKitId is defined, that's the view root and a
factory implementing decorator pattern. So we add
public class UIViewRoot
{
public String getSkinFamily();
public void setSkinFamily(String skinFamily);
}
public abstract class SkinFactory implements FacesWrapper<SkinFactory>
{
public abstract void addSkin(String skinFamily, String
renderKitId, Skin skin);
public abstract Skin getSkin(String skinFamily, String renderKitId);
public abstract Collection<String> getSkinFamilies();
public SkinFactory getWrapped()
{
return null;
}
}
*Sub-issue 1.1: Default SkinFactory implementation behavior*
Additional Exceptions
public class SkinException extends FacesException
{
// Provides all 4 common Exception constructors
}
public class SkinInitializationException extends SkinException
{
// Provides all 4 common Exception constructors
}
Additional interface
/**
* Implementations of this interface are in charge of creating Skin
instances in an implementation specific manner.
*/
public interface SkinLoader
{
/**
* Creates a new <code>Skin</code> instance the specified
skinFamily and parameters. There's no garanteed as of what
* parameters will be present in the Map argument and the creation
should fails if those parameters are not correct
* for the specific implementation.
*
* @throws SkinInitializationException if the provided parameter
are not enough to create the <code>Skin</code> instance
*/
public Skin loadSkin(String skinFamily, String resourceBundle,
Map<String, String> parameters);
}
Addition to faces-config.xml:
<application>
<default-skin-family/>
</application>
<skin>
<skin-family/>
<render-kit-id/>
<description/>
<skin-loader-class/>
<resource-bundle/>
<skin-loader-parameters>
<map-entries/>
</skin-loader-parameters>
</skin>
Note that this solution makes RenderKit.getSkin more an utility method
than anything, except if an extension decides to override it (would
probably be the casew ith Trinidad, at least to start to use
trinidad-config.xml, or later to add .pda or .desktop to the current
skinFamily).
Also, the framework should probably provide some implementations of
SkinLoader out-of-the-box, most likely ClassSkinLoader expecting a
skinClass parameter and a CSSSkinLoader receiving a cssResourcePath
parameter. (Trinidad would mostl ikely provide an XSSSkinLoader for
example)
*Issue 2: Skin content rendering*
How should rendering be handled? Should it be handled as an added
ResourceDependency just before rendering or as an additional contract
for the default HtmlRenderer for HtmlHead? The latter sounds better,
but how should it be implemented? target would obviously be "head",
but then a public Resource Skin.getContentResource() method could be
required and this might imply some changes to the ResourceHandler
specification so that it can locate the Skin instance in order to be
able to serve the Resource's content (effective CSS content) to the
agent. I like the Resource version, but I have more thinking to do
about how to implement this, maybe involving a reserved
resourceIdentifier structure/syntax
*Issue 3: Skin features*
Additional class
public abstract class Icon extends Resource
{
/**
* Gets the key within the skin's ResourceBundle to the
description of this icon. The description can be used
* as an alternate text for the icon to improve accessibility for
example.
*/
public abstract String getDescriptionKey();
/**
* Gets the name of this icon.
*/
public abstract String getName();
/**
* Gets the effective style class of this icon within the skin.
*/
public abstract String getStyleClass();
}
public interface Skin
{
public Icon getIcon(String iconName);
public String getProperty(String propertyKey);
public String getResourceBundle();
public String getStyleClass(String selectorKey);
}
*Sub-issue 3.1: style class / property / icon access strategy*
Trinidad uses String keys to access style classes within the skin.
That strategy is very flexible, but alas very slow, thanks to
String.hashCode() being linear and uncached. So, even if
String.intern() is called on all selectors within a given skin, the
gain will only be on String.equals, making it O(1) instead of O(n).
Even if this is better than nothing, especially it there's a
collision, the overall complexity of looking up a selector remains
O(n) with the length of the key. So, the overall cost of skinning with
this strategy is averageLengthOfKey * averageAmountOfComponentPerPage
* averageAmountOfHtmlElementPerComponent (assuming each element gets
its own style class which is required for maximum customizability).
Another, but a little bit more complex, solution is also possible
using a "dynamic enum" alike to PropertyKey in Trinidad. So I'd like
something like:
public abstract class SkinKeyPart
{
public String name();
public int ordinal();
}
public class Namespace extends SkinKeyPart
{
/**
* Find or create the Namespace instance with the specified name.
*/
public static Namespace getInstance(String name);
}
public class ComponentKey extends SkinKeyPart
{
/**
* Find or create the ComponentKey instance with the specified name.
*/
public static ComponentKey getInstance(String name);
}
public class ComponentPartKey extends SkinKeyPart
{
/**
* Find or create the ComponentPartKey instance with the specified
name.
*/
public static ComponentPartKey getInstance(String name);
}
public class ComponentStateKey extends SkinKeyPart
{
/**
* Find or create the ComponentStateKey instance with the
specified name.
*/
public static ComponentPartKey getInstance(String name);
}
public abstract class SkinNode
{
public abstract String[] getStyleClasses();
public abstract String getProperty(String propertyName);
public abstract Icon getIcon(String iconName);
public abstract SkinNode getSubPart(ComponentPartKey partKey);
}
public interface Skin
{
/**
* Still exists to get properties global to the whole skin if
someone ever need that feature
*/
public String getProperty(String propertyName);
/**
* No change here
*/
public String getResourceBundle();
public String getSkinNode(Namespace ns, ComponentKey component);
public String getSkinNode(Namespace ns, ComponentKey component,
ComponentStateKey state);
public String getSkinNode(Namespace ns, ComponentKey component,
ComponentStateKey... states);
}
That solution has the advantage of being very fast, actually a real
O(1) for every node access assuming the Renderer gathers the keys it
needs during instanciation. Then the "root" SkinNode could probably be
located using a base Renderer class and passed to a custom encoding
method (Trinidad's encodeAll for example). Of course, the skin loaders
would have to create skin instances structuring the selectors
accordingly. A base class for Skin with such feature should probably
be provided if this solution is wanted in order to make new
implementation of Skin easier to create.
*PENDIND issues:*
PENDING: Should the path leverage the Resource API instead and receive
a library-name, library-version, resource-name and resource-version
instead?
PENDING: A CompositeSkin class should probably be provided and most
likely usable from the configuration, should it be using a specific
loader with parameters or have a fully-fledged tag like
<composite-skin> for example? The use of such skin implementation
would be for users using multiple libraries, each with very different
RenderKit implementations. A composite RenderKit class could also be
useful here.
PENDING: Should Trinidad's SkinAddition notion be considered?
SkinAddition provides a way to add skin elements to an existing skin
based on its id. It would be possible to leverage that by using the
skin-family/render-kit-id tuple instead. Another option would be to
simply have the default SkinFactory implementation to automatically
agreggate the Skin instances with the same skin-family and
render-kit-id into a CompositeSkin instance.
PENDING: Should additional metadata be added to <renderer> tag to
define the supported skin selector for tooling purpose? This point is
dependant on the content of the Skin interface and the strategy chosen
to access the properties/style class of a given component and/or one
of its sub-element.
PENDING: Should the effective CSS properties stay accessible in memory
or otherwise from the Skin instance? If so, should they be modifiable?
I tend toward no for both for performance issues. Of course this could
be also left as an optional operation of the interface, throwing
UnsupportedOperationException in most case but at least leaving the
possibility for special implementations requiring it. This point also
impact issue 2 in some ways.
PENDING: About Icon, it should probably also leverage the Resource
API, how should that be implemented?
PENDING: Icon's styleClass, should the instance directly return an
effective class or should it rather return a selector that would then
be looked up from the Skin? I prefer the latter, but then there's the
Stirng issue so maybe the icon would have to return an optimized
selector key as mentioned in 3.1
PENDING: Solution to issue 3.1, although very fast and flexible, can
be an linkage nightmare. For example, a node for component "inputText"
with states "disabled" and "required" should be able to link to the
component part node for part "label" of "inputText" without any state
defined if needed. The performance gain of 3.1 is great, the
implementation complexity isn't. Still, personally I don't have any
problem throwing complexity at implementor's head if the users can
feel the difference. Another option would be to remove the "state"
complexity part and have a predefined style class syntax for those.
That solution is what Trinidad does. However, this solution is not as
flexible as it doesn't allow to switch Icon dynamically depending on
the component's state.
PENDING: Solution 3.1 don't allow state to be applied on the
component's sub parts. Most of the time it's not an issue, but we have
one such use case with Trinidad's train component where the train
itself doesn't have a state, but each of its station (sub-part) can be
visited/unvisited and/or selected and/or disabled. If 3.1 is kept,
should it be tweaked even more to allow a sub-part state on a per part
basis as well? This makes the linkage a nightmare on Elm Street
candidate, althoguh adding more potential to the Skin
PENDING: Trinidad supports a "selector redirect" feature that is used
with the delegate renderer architecture to have specified selectorKeys
transformed into a different one. This is implemented as a Map<String
oldName, String newName> redirection map. Should such feature be
standardized? If so and 3.1 is adressed then it will have to be well
thought about. Also, trinidad support a single of those map applied as
a set on the skin instance, I believe it should be a push / pop
instead if standardized.
PENDING: Should there be any inheritance/cascading behavior defined by
default? I tend toward no here.
PENDING: Should the skin have a getTranslatedResource(FacesContext
context, String resourceKey) instead of the getResourceBundle method?
That's about it for now. My main blocker is 3.1 and I'd like to come
up with something better for it before presenting it to the EG, same
holds true with various links to the Resource API (icons, the skin
itself, etc)
Regards,
~ Simon
On Fri, Dec 5, 2008 at 10:33 AM, Andrew Robinson
<[EMAIL PROTECTED] <mailto:[EMAIL PROTECTED]>>
wrote:
> All other frameworks use
> component attributes for this, but Trinidad puts it in these
> non-intuitive skinning keys.
Sorry, had one more comment right after I hit send again. Maybe just
having default component attributes for a web app would satisfy the
need. Like a way to say 'partialSumbit' should be defaulted to true on
all tr:commandLink. Then all the skin properties could be converted to
attributes and have their defaults set in a common location (like what
the skin does).
This would be much more useful and flexible as then page developers
can make exceptions to the rule.