The Wobzilla component-architecture is based on the concepts of the Avalon component-framework. A central principle of Avalon is "inversion of control": The componnets are rather passive and get their instructions from the superiour container (which can be a component itself). The communication between container and the component-instances managed by the container takes place over well defined interfaces (explained below). If a component needs a specific instruction, it must implement the method specified in the appropriate interface. This method will be called by the container at the right time. According to the avalon documentation this works like in the military.
The configuration is analog to the component hierarchie a tree
(more precisely a DOM-Tree). Each node of the tree is a valid
"sub"-configuration itself. If a component implements the
configure
method, it will be assigned the proper
sub-configuration of the configuration-tree.
The configuartion node of a container contains the configuration of its child-components but it doesn't have to evaluate this sub-configurations. The container just passes them to its childs. In that way a complex component-configuration can be described without the components beeing complex themselfs. This is a successful application of "divide and conquer".
If a component implements the contextualize
method
it signals that it needs information from its context.
Typical context entries in wobzilla are the language or special
variables provided by a container. A container has further the possibility
to replace or extend its assigned context, and provide this new sub-context
to its childs.
See Context API.
Each container can provide a set services to it childs through
the Service Manager
object. As with context objects the container can also extend, restrict
or replace the services provided by its parent, to offer different
services to its childs. The childs lookup these services during
the service
method.
TODO Explain Logging. Avalon Logger.
TODO explain disposable.
In addition to the avalon-methods (which are optional for each
component) each view must implement the init
method. In the init
method, the container assignes
the output-model and the data-model to the view. The following
table explains the arguments of the init
-method:
argument | type | description |
---|---|---|
modelNode | node |
data-node which should be modified and/or displayed by the view. The view is not bound to edit only the passed data-node. It can also edit another node in the data-model, e.g. a sub-node or a parent-node of modelNode. |
parentOuputNode | node |
the node in the outputTree to which the view should append its output. In contrast to the data-nodes, here the view is only allowed to append to the parentOutputNode. Accordingly only output-nodes that were appended by the view itself can be deleted. |
insertAfter optional | node |
the node, after which the view should append its output |
The interation between containers, components and the data- and output-tree is shown in the followning collaboration-diagram:
The validate
method instructs the view to validate
any changes made by the user in the user-interface and write back these
changes to the data-model. The view must use the wobzilla.transaction service to do this.
validate
is the only method that a view
should call itself. valdiate
could be called as a
reaction to specific GUI-events, for instance when a text-field
looses focus, or a button is pressed.
validate
should return false
, when the
validation failed and true
when there was no
validation or if validation was successful. Containers should
forward the validate
calls to their childs. Before
saving a document, the validate
method of the root-view
is called and WobzillaEngine
will only save the document if that method returned true
.
See wobzilla.variableHandler
for further information about specifying global and local constraints and
displaying error-messages.
To get notified when the data-model changes, the component should
implement the requery
-method. This method is
called by the container every time a change in the data-model occurs.
If the view is a container itself it should forward
the requery
notifications to their children to achieve
a successive refresh.
Note: To avoid reentrance-problems and inconsistencies, no modifications of the data-model are allowed during the processing of the requery
method.
Passed to the requery
is a list with all
elementary node-operations that occured on the data-model, in the order of
their execution. This list is called
wobzilla.changeLog
.
Elementary node-operations are:
A node was inserted into the tree. Additional arguments:
argument | type | description |
---|---|---|
target | node | The node that was inserted |
parent | node | The parent-node of the node being inserted. |
insertBefore | node | The node,
that follows the newly inserted node. This attribute is null if
the node was inserted at the last position. |
A node was removed from the tree. Additional arguments:
argument | type | description |
---|---|---|
target | node | The node that was removed |
parent | node | The parent-node of the node being removed |
removedBefore | node | The node that had followed the removed node before it was removed. This attribute is null , if the removed node was the last child. |
The attribute of an element-node has been changed. Additional arguments:
argument | type | description |
---|---|---|
target | node | The element-node to which the attribute belongs. |
attrName | node | The name of the attribute |
newValue | node | The new attribute-value |
oldValue | node | The old attribute-value |
The most important goal of Wobzilla is an easy extensibility. Therefore the interfaces are kept simple, as the following example of integrating a new component shows.
The new component MyCheckBox
should allow the manipulatation of boolean
values. The configuration of MyCheckBox
only specifies the data-node in the select
attribute.
<wbz:container select="diplomarbeit"> <wbz:view class="MyCheckBox" select="@abgegeben"/> ..
The code for the MyCheckBox
-component is short:
function MyCheckBox() { var _xpath; // xpath expression matching the model-node var _outputNode; // html-checkbox element var _model; // node in the data-model var _resolver; // wobzilla.xpath processor var _transaction; // wobzilla.transaction handler var _log; var _context; this.enableLogging = function(logger) { _log = logger; } this.contextualize = function(context) { _context = context; } this.service = function(serviceManager) { _resolver = serviceManager.lookup("wobzilla.xpath"); _transaction = serviceManager.lookup("wobzilla.transaction"); } this.configure = function(config) { _xpath = config.getAttribute("select"); } this.init = function(parentOutputNode,parentModelNode,insertAfter) { // create checkbox and append to output tree _outputNode = document.createElementNS(HTML_NS,"input"); _outputNode.type = "checkbox"; WBZ_insertAfter(parentOutputNode, _outputNode, insertAfter); // get model-node and set checkbox state to model-value. // This approach only works for attributes. See // code of WbzTextField for the handling of textnodes _model = _resolver.getNodeSafe(parentModelNode, _xpath, _context); _outputNode.checked = (_model.nodeValue == "true"); // register for checkbox-click events _outputNode.addEventListener("click",this.validate,true); } this.requery = function(changeLog) { // update checkbox state on model change _outputNode.checked = (_model.nodeValue == "true"); if (_log.debugging) _log.debug("new checkbox state: " + _model.nodeValue); } this.validate = function() { var newValue = ( _outputNode.checked) ? "true" : "false"; if (newValue != _model.nodeValue) { _transaction.start(); WBZ_setNodeValue( _model, newValue); _transaction.commit(); } return true; } }
The integration in the Wobzilla redo/undo- and update-mechanisms
takes place through the requery
-method. If
the data-node changes (caused by an user interaction or an undo)
the requery
method gets called and the
CheckBox-state will be adjusted. To use MyCheckBox copy the file
to js/views/MyCheckBox.js and add the following line in wobzilla.xul
.
<script type="application/x-javascript" src="js/views/MyCheckBox.js"/>
Testing and debugging of components can be done with Venkman (the Mozilla-Debugger). Vekman also offers a very good Profiler.
Additional assistance is provided through the Wobzilla logging mechansim. There is a debug-menu, and you can use id-attributes in the configuration-files for context-sensitiv logging. This enables you, to differentiate the log output from a TextField containing the first-name from a TextField containing the last-name.