Simplify Skinning ================= :Author: Philipp von Weitershausen, philikon@philikon.de :Status: IsProposal :Version: 4 :Date: $Date$ :Original: $URL$ 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 mere ``IBrowserRequest`` request type. If no layer is specified, the ``IDefaultLayer`` layer is assumed. Layers are distinguished from regular interfaces by providing the ``ILayer`` interface which extends ``IInterface``. * **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 ``ISkin`` interface which extends ``IInterface``. 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:: 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:: Now you can register views and resources that are available in the new skin. You have to put them on the *layer*, though:: 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:layer`` and ``browser:skin`` directives anymore. These will disappear. * Layers are simple interfaces extending ``IBrowserRequest``, ``ILayer`` as a marker is removed and rendered useless. Layer interfaces will be identified (e.g. in ZCML) using their **dotted import name**. * 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. This is in analogy to the ``type`` argument of the ``view`` directive adds some **more consistency to the ZCML directives** in Zope 3. It defaults to ``IDefaultLayer`` (like ``layer`` does currently) and only accepts interfaces that are or extend ``IBrowserRequest``. * Layer interfaces that should also be available as skins can be marked with ``IBrowserSkinType`` (previously ``ISkin``) and registered as named utility (very much like what ``browser:skin`` does currently). On the renaming of ``ISkin`` to ``IBrowserSkinType``: This is to indicate that a) the skins concept is **browser-specific** and b) this interface is an **interface for interfaces** (extending ``IInterface``). Naming these interfaces ``ISomethingType`` (e.g. ``IContentType`` or ``IMenuItemType`` as well as ``queryType()``) is an informal convention in Zope. * As a bonus, we will also provide a vocabulary called ``Browser Skin Names`` which returns the names of all available skins (=interfaces that provide ``IBrowserSkinType``). This would simply be based on ``zope.app.component.vocabulary.UtilityVocabulary``. * The ``++skin++`` namespace traverser will not change its behaviour. Implementation and backward compatability plan ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Changes in Zope 3.3/2.10: * ``zope.publisher.interfaces.browser.ISkin`` is renamed to ``IBrowserSkinType``. * The ``type`` argument is introduced to ZCML ``browser`` directives. * The (optional) ``name`` argument is introduced to the ``interface`` directive. * The vocabulary ``Browser Skin Names`` is provided. Deprecated in Zope 3.3/2.10 and slated for removal after 12 months (Zope 3.5/2.12): * the ``ILayer`` interface * the ``browser:layer``, ``browser:skin`` ZCML directives * the ``layer`` argument to ZCML ``browser`` directives 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:: 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:: 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``):: 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 Names`` defined. The result would be a conflict in the ZCML configuration. Implementation status --------------------- The full Zope 3 implementation of this proposal, except for the ``type`` argument of browser directives and the ``Browser Skin Names`` vocabulary, is available in the ``philikon-simplify-skinning`` branch. It is awaiting review but otherwise ready for merge. The Zope 2 (Five) implementation will have to wait until Five 1.4 is branched. 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.