Simplify skinning
Author
Philipp von Weitershausen, philikon@philikon.de
Status
Version
4
Original
http://codespeak.net/svn/user/philikon/SimplifySkinning.txt
Current situation
Being inspired by the CMF, Zope 3 has a system for customizing browser views by overriding existing ones. This system is based on two concepts:
- Layers are interfaces extending
IBrowserRequest. Views can be registered for a certain layer by being registered for the layer interface as request type instead of a mereIBrowserRequestrequest type. If no layer is specified, theIDefaultLayerlayer is assumed.Layers are distinguished from regular interfaces by providing the
ILayerinterface which extendsIInterface. - Skins are interfaces that aggregate one or more layers by
simply extending the layer interfaces. The order of the interface
inheritance defines the layer ordering inside the skin. Skin
interfaces obviously also extend
IBrowserRequest, if not directly then indirectly. Request objects can be marked with a skin by directly providing it. This can happen through various methods, e.g. setting a default skin or the++skin++namespace traverser.Skins are distinguished from regular interfaces by providing the
ISkininterface which extendsIInterface.
Both layers and skins can have simple aliases with which they can be
referred to in ZCML, e.g. default for the IDefaultLayer layer
or Rotterdam for the zope.app.rotterdam.Rotterdam skin. These
aliases are also used by the ++skin++ namespace traverser. The
layer/skin machinery uses the utility registration to look up layers
and skins by their simple aliases.
Layers and skins don't necessarily have to be created in Python code.
The browser:layer and browser:skin ZCML directives can create
the interfaces on the fly, though in which case they won't be
available for import in Python, though. In such case they will be
registered with the utility registration as a named utility with their
simple alias as the name.
Example
In order to customize an existing skin, say Rotterdam, you currently have to register a new layer. A typical usage would be to use the simple alias as an identifier and not construct it in Python code:
<browser:layer name="shanghai_layer" />
Then you have to make a skin based on Rotterdam's layer and the new layer. Again, typical use-cases don't create the skin in Python code:
<browser:skin
name="ShanghaiSkin"
layers="shanghai_layer rotterdam default"
/>
Now you can register views and resources that are available in the new skin. You have to put them on the layer, though:
<browser:resource
name="zope3logo.gif"
file="shanghailogo.gif"
layer="shanghai_layer"
/>
You will see your new skin with its custom logo by putting
/++skin++ShanghaiSkin/ in your URL.
Problem
From a technical point of view, the distinction that layers are interfaces for which views are registered whereas skins are interfaces with which views are looked up seems arbitrary. It is also unnecessarily complicated. (From an end-user point of view, this distinction might still be wanted, but this doesn't mean the implementation behind it has to reflect that.)
Imagine you want to customize a few views on an existing skin (this is a common deployment problem). You would do, as above in the example,
- create a new layer,
- register your views for this layer,
- then create a new skin based on your new layer and the layers of the skin you want to customize.
Why couldn't you simply create a skin that extends the old skin? And why coudln't you simply register your views on that custom skin of yours directly? Why do you have to do it on a layer?
Having to go through the extra hoops of making a layer that only
serves the creation of a new skin is an unnecessary indirection.
Seeing that the shanghai_layer from the example above is related to
the ShanghaiSkin might not seem obvious to all Zope 3 programmers
(especially when the names are not as well chosen). Also, realizing
that the browser:layer and browser:skin directives use interface
semantics is also not easy to understand just from the usage of their
directives.
That leads us to another aspect of indirection on a totally different level: In the end, layers and skins are nothing but interfaces. They are code constructs, they could possibly have docstrings, would want to show up in generated API documentation, etc. There is thus a strong reason to define these in Python directly. ZCML's task on the other hand is not to construct components but to take care of the wiring. ZCML hides the simplicity of layer and skins (they're simply marker interfaces on the request) through extra ZCML directives that are not necessary when standard Python code and simpler ZCML directives can take over.
Goals
- Make the customization of existing skins easy and straight-forward without creating too many constructs just to satisfy the framework.
- Retain the flexibility of grouping several layers into a skin, reusing layers from another skin, etc.
- Still be able to distinguish between layers and skins for the end-user: Layers are an implementation detail that the end-user doesn't care about, skins represent the look-and-feel of an application and could be chosen explicitly by the end-user.
- Make the creation and registration of layers and skins straight-forward for the Component Architecture programmer.
Proposal
I propose to get rid of skins as a separate type of component and simply use layers everywhere. Like views are special kinds of adapters, skins would still be around as a conceptual term. In the implementation they will only be special kinds of layers.
Layers, on the other hand, do not need to be specially identifiable.
They are simple interfaces extending IBrowserRequest. They do not
need an extra marker and needn't be known under a human-readable alias
(as opposed to skins).
Architecture
- Skins and layers will have to be defined through Python as
regular interfaces, not through the
browser:layerandbrowser:skindirectives anymore. These will disappear. - Layers are simple interfaces extending
IBrowserRequest,ILayeras a marker is removed and rendered useless. Layer interfaces will be identified (e.g. in ZCML) using their dotted import name. - The
layerargument ofbrowserZCML directives will be deprecated in favour of a newtypeargument which will allow you to define the layer interface. This is in analogy to thetypeargument of theviewdirective adds some more consistency to the ZCML directives in Zope 3. It defaults toIDefaultLayer(likelayerdoes currently) and only accepts interfaces that are or extendIBrowserRequest. - Layer interfaces that should also be available as skins can be
marked with
IBrowserSkinType(previouslyISkin) and registered as named utility (very much like whatbrowser:skindoes currently).On the renaming of
ISkinto 'IBrowserSkinType?': This is to indicate that a) the skins concept is browser-specific and b) this interface is an interface for interfaces (extendingIInterface). Naming these interfacesISomethingType(e.g.IContentTypeorIMenuItemTypeas well asqueryType()) is an informal convention in Zope. - As a bonus, we will also provide a vocabulary called
Browser Skin Nameswhich returns the names of all available skins (=interfaces that provideIBrowserSkinType). This would simply be based onzope.app.component.vocabulary.UtilityVocabulary. - The
++skin++namespace traverser will not change its behaviour.
Implementation and backward compatability plan
Changes in Zope 3.3:
zope.publisher.interfaces.browser.ISkinis renamed toIBrowserSkinType.- The
typeargument is introduced to ZCMLbrowserdirectives. - The (optional)
nameargument is introduced to theinterfacedirective. - The vocabulary
Browser Skin Namesis provided.
Deprecated in Zope 3.3/2.10 and slated for removal after 12 months (Zope 3.5/2.12):
- the
ILayerinterface - the
browser:layer,browser:skinZCML directives - the
layerargument to ZCMLbrowserdirectives
Example
The skin customization from above will be much simpler to accomplish. You only have to create a new skin interface inheriting from Rotterdam's skin interface:
from zope.app.rotterdam import Rotterdam
class ShanghaiSkin(Rotterdam):
"""Wo zhu zai Shanghai"""
Of course, we still need to make this a proper skin (right now it's just an interface which puts it on the same level as layers) so that it's available under a human-readable name. We can do that in ZCML using two standard Component Architecture directives now:
<interface
interface=".interfaces.ShanghaiSkin"
type="zope.publisher.interfaces.browser.IBrowserSkinType"
/>
<utility
component=".interfaces.ShanghaiSkin"
provides="zope.publisher.interfaces.browser.IBrowserSkinType"
name="ShanghaiSkin"
/>
As a shortcut, we can also just use the interface directive and
pass the new name parameter. The following one directive has the
same effect as the two above regarding the skin registration:
<interface
interface=".interfaces.ShanghaiSkin"
type="zope.publisher.interfaces.browser.IBrowserSkinType"
name="ShanghaiSkin"
/>
Registering views and resources is not any different now, but you can
simply register them on the skin directly (notice the type
parameter instead of layer):
<browser:resource
name="zope3logo.gif"
file="shanghailogo.gif"
type=".interfaces.ShanghaiSkin"
/>
As you can see, we didn't have to create an extra layer just to create a custom skin. We were also able to use standard Component Architecture ZCML directives instead of custom ones whose special syntax the developer needs to remember additionally.
Risks
- Other applications could already have a vocabulary called
Browser Skin Namesdefined. The result would be a conflict in the ZCML configuration.
Implementation status
This proposal has been implemented on the Zope 3 trunk, except:
- the
layerargument of browser directives wasn't renamed totype - a vocabulary of browser skins called
Browser Skinswas implemented instead ofBrowser Skin Names.
The Zope 2 (Five) implementation has yet to follow on the Five trunk.
Things that this proposal does not cover
- Some people have expressed that the way the CMF skin machinery lets one drop resources and templates into a folder that represents a skin layer is vastly simpler than the tedious process of registering many resources at once in Zope 3. While this is probably true, improving the way resources are registered is out of scope of this proposal.
... --drzoltron, Fri, 22 Dec 2006 02:27:10 -0800 reply
Could you give an example about the new-style layer definition ? (The layer argument of browser ZCML directives will be deprecated in favour of a new type argument which will allow you to define the layer interface. )
