|Redo skin framework
|Trevor Parscal, Timo Tijhof
We would like to create a skin framework to:
- Make modification, extension and creation of skins quicker and less prone to error
- Achieve compatibility between skins through an object-based API on the server and client
- Liberate developers from having to maintain HTML output compatibility between skins
Currently, most MediaWiki UI elements don't have APIs, or have poor ones, and instead the HTML output by the skins for these elements is treated as the API. The HTML output of skins is kept as similar as possible between each skin as to support the many assumptions that are made by core, extension and gadget software that modify them on the client. On the server side, the few features we do have are centered around this approach of nearly identical HTML output being treated as the API.
This dependency is extremely fragile and resistant to change. Changing a skin or even just adjusting a style can easily involve updating code in hundreds of locations – and there's no way to be sure which assumptions have been made – discouraging iteration and innovation. New skins that do not have a nearly identical HTML structure will fail to work with many features and take a very long time to come to maturity, discouraging creation. In the best case, features try to work around minor differences, but still make lots of assumptions about the HTML. In the worst case, a skin's HTML output is so different that nothing works unless it is written specifically for it.
Skins are assembled using a collection of view objects, each responsible for a different set of controls on the page. The view objects render controls based on model objects that represent the site, the page and the user. As model objects are modified on either the server or the client, view objects automatically update their rendering accordingly. The specifics of how view objects render model information is changed by extending the view objects for a particular skin. A skin is created by specifying which view objects to include, the order in which to include them and styles to apply to them. View objects are extended or replaced completely by providing a server and client implementation and including them in the skin. Other systems interact with the skin on either the server or the client by modifying the page, the site or the user model objects through an API.
On the server, rendering is not performed until the final state, allowing interactions with the site, the page and the user model objects to be completed before responding to the client. The model objects are serialized and embedded into the page for the client to use. On the client, if the client is capable, the model is unserialized and view objects are generated and bound to the DOM which the browser has built from the HTML the server generated. Further modification of the model objects triggers changes in the view objects.
Throughout the remainder of this document, view objects are referred to as "widgets", and model objects as "models". On the server, widgets respond to events emitted from models and control their rendering accordingly. On the client and the server, as models are modified, events are emitted that widgets listen and respond to. On the client, widgets also handle events from user interactions. This combination of responsibilities makes a widget a combination of a view and a controller. The purpose of a widget is to provide a general interface and mechanisms for both producing the initial rendering and making subsequent modifications of that rendering. This abstraction provides the widget author with the authority to use whatever means desired or necessary to meet their objective within a particular skin, and be able to achieve consistent behavior across browsers and devices. This is distinctly a superset of the functionality of a template, which only produces an initial rendering. Widgets may provide any mechanism, including templates, to produce initial or even subsequent renderings; the general interface a widget provides is specifically designed to abstract such mechanisms.
- Skin class which manages a collection of widgets and provides access to the models they will render
- Base set of widgets that provide common sets of controls and are optimized for skinning and extending
- Server-side API for defining static rendering and behavior of widgets
- Client-side API for defining dynamic rendering and behavior of widgets
- Convert Vector, Monobook, Modern and Minerva to the new system
- Introduce a radically different skin (doesn't need to be the "next" skin) as a test to prove flexibility
- Document the server and client APIs
The implementation details are intentionally unspecific at this point in time. The first step in the process will be to audit existing skins and extensions to discover patterns and anti-patterns related to rendering, extending and modifying skins. Existing systems, especially those that are mature and capable, will be integrated into the design and implementation. Additional consideration will be given to the general use of this system for other features and conformance to existing and emerging standards.
Key features of a skin
Although most implementation details are deliberately unspecified, there are some guiding design principals that are already known and will be adhered to.
- Managed output
The output buffer is not written to directly. The skin provides an abstraction for the placement of content within a page, and is able to enforce security and ensure correct and balanced HTML output.
- Data driven
On both the client and the server, a skin provides widgets with a set of models that represent the site, page and user. Rendering is based on these models, and changes to these models causes changes in rendering. This abstraction ensures that any software that can make changes these models will be naturally supported by any skin.
The behavior of a skin, and the widgets it contains, is designed to be mutable by creating a subclass. Widgets are referenced by a symbolic name, allowing a skin to be partially modified by extension software by providing an alternate version of a widget. This loose coupling is accomplished using a factory pattern and class based inheritance.