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]> 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.
>