Components typically need to be connected to other components before they can perform a variety of tasks, e.g. a button must be connected to another component before it can do something useful. Each of those components will have one or more references to other components that are expected to be of a specific type. e.g. an event handler component references a component that is an event target, a list view has references to a model, a renderer and a list cell layout factory.
Each component can implement a number of types. A named component class defines a component type of the same name that is an extension of the types that it implements. An anonymous class does not define a component type and not all component types are implemented as component classes. Some component types are simply just names for a specified set of client-side functionality, e.g. methods, properties. A component type can inherit from multiple other types, and because the types are interfaces, a type can be inherited more than once.
On the server a type is identified by its name which consists of a namespace and a local name. In a document it is represented and resolved as an xs:QName. On the client a type corresponds to a specific client-side interface.
Custom types must not be created using the Volantis namespaces.
A cx:component-class element defines the structure of all components of a particular type, specifically the references it has to other components.
A class definition provides the following information:
The optional name attribute specifies the name of the class. The name defines a new component type that inherits from those types that it implements. Note that every component class that is used within a page and which has a name is registered in the global type registry.
The optional implements attribute defines a set of component types that the class implements. The list of components must be consistent with the client-side implementation as otherwise it will prevent the server from detecting errors correctly.
The optional defines attribute specifies a set of aspects completely defined by the component class. The component class author can indicate which aspects of the component class have been fully defined and which have not. If a component does not define an aspect or only defines it partially, i.e. defines some references but not all, then it should not declare that it defines that aspect completely but the information that is provided will be assumed to be correct. When the validation mechanism cannot find some information in the class it uses this information to determine whether it is an error or not. For example, let's assume a component instance uses a reference that is not defined in the component class. If the cx:component-class element indicates that it defines 'reference-types', then that is an error, otherwise it is controlled by the validation mode. Refer to the topic entitled Validation for more information.
The 'reference-types' aspect covers the following information about each reference: name, component types, structural relationship, and cardinality. Refer to the section entitled References for more information.
The 'all' aspect indicates that a given component class definition defines all aspects of the component class.
A cx:component-class element can contain mcs:script elements to include either script policies or an inline script. As usual, script policies are treated as libraries and so will only be added to the output document once irrespective of how many times they are used. On the other hand inline scripts will be added each time they are used and therefore it is the page author's responsibility to ensure that they are only included in the input page when needed.
A cx:client-class element provides information about the client-side JavaScript representation of a component class.
The optional name attribute specifies the name of the client-side class that implements a given component. If specified, the name is used to generate the code needed to construct the component; otherwise a component instance specific creation code must be provided using a cx:client-create element. Refer to the section entitled Component instances for details.
The optional start-method attribute defines the name of a method on the client-side class that should be invoked during the starting phase of the page construction process. If not specified, a component instance specific code can be provided using a cx:client-start element. Refer to Component instances for more information.
A cx:reference element defines the following characteristics about a reference that a component has to another component:
The name attribute specifies the name of the reference. This is used as the name of the field into which the reference is stored on the client if it is not specified explicitly using cx:client-reference.
The component-types attribute defines a set of types that the referenced component must implement. If the set is empty, then it can reference any component.
The optional structural-relationship attribute defines the implicit structural relationship between the component to which the reference point belongs and the component to which the reference point refers. The attribute supports the following values: 'parent' and 'child'. The value of 'parent' indicates that the reference must automatically be bound to the parent component if it implements all the specified types. The value of 'child' indicates that the reference must automatically be bound to a child component if it implements all the specified types. If not specified, then no implicit relationships are supported and therefore the reference must be explicitly set through some custom JavaScript on the client.
The min-occurs attribute specifies the minimum number of occurrences of the reference in the component instance. If the value of the attribute is set to '0', then a reference is optional. If it is set to '1', then a reference is required. If a reference is not specified, then the server-side behavior will depend on the validation mode.
A cx:client-reference element provides information about the client-side representation of a reference. At present this is restricted to how to store the result of resolving the reference on the client-side. If this is not specified, then by default the reference is stored in a field with the same name as the reference. There are three possible ways in which a reference can be stored in the JavaScript object representation of the component: as a field, as an observable property, or through a one argument setter.
Each component instance consists of the following:
A reference to a component class. Custom components can do it either through containment or by id. See also: cx:component.
Explicit references to components. Custom components use the attributes on the cx:ref element.
A component instance can also provide some client-side functionality:
A cx:client-create element can contain a block of code for creating the client-side instance. If provided, the code overrides the default mechanism of using the JavaScript new operator to create an instance of the class defined by a cx:client-class element. This must be provided if the component class does not specify the name of the client-side class. The code is wrapped in a no argument function that is stored in the global function registry and the value returned from the function is assumed to be the client-side instance of the component. For example:
<cx:client-create>
return new MyItem(1,2);
</cx:client-create>
will be turned into the following JavaScript:
V$F.a = function() {
return new MyItem(1,2);
}
A cx:client-start element can contain a block of code for starting the client-side instance, invoked during the starting phase of the page construction process after all the references have been resolved. If provided, this overrides the method specified in the start-method attribute of the cx:client-class element. The code is wrapped in a function that is stored in the global function registry. The function has a single parameter which is the reference to the client-side component instance and the name of the parameter is 'c'. For example:
<cx:client-start>
c.start(1,2);
</cx:client-start>
will be turned into the following JavaScript:
V$F.a = function(c) {
c.start(1,2);
}
Support for the custom component markup is represented by the following feature:
<html xmlns="http://www.w3.org/2002/06/xhtml2"
xmlns:cf2="http://www.volantis.com/xmlns/2009/07/cf2"
xmlns:cst="http://www.volantis.com/xmlns/2009/07/cf2/template"
xmlns:cx="http://www.volantis.com/xmlns/2010/07/cf2/cx"
xmlns:mcs="http://www.volantis.com/xmlns/2006/01/xdime/mcs"
xmlns:my="http://www.mycomponents"
xmlns:ui="http://www.volantis.com/xmlns/2009/07/cf2/ui"
xmlns:car="http://www.cars/">
<head>
<title>Custom Component Example</title>
<meta property='cf2:validation-mode' about='#PoorDriver'>strict</meta>
<!--
! This page makes use of Observable Property Prototype and so should explicitly indicate
! that by providing an mcs:script element that references an mscr file that imports those
! features. Instead this relies on the ui:button element importing the required feature
! into the page. This is probably not the best way but is allowed as a button is
! explicitly defined as importing that feature.
!-->
<!--
! Car Class
!-->
<cx:component-class defines="reference-types" name="car:Car" id="Car">
<mcs:script type="text/javascript">
// ==================================================
// JavaScript representation of Car
// ==================================================
var Car = V$.Class.create();
Car.mixin(V$.cf2.OPSet);
V$.cf2.OPProto.add(Car, "owner", {});
Car.methods({
initialize: function() {
V$.cf2.OPProto.init(this);
},
setEngine: function(engine) {
this.engine = engine;
},
start: function() {
// Start engine.
}
});
</mcs:script>
<cx:client-class name="Car" start-method="start"/>
<cx:reference name="engine" structural-relationship="child"
component-types="car:Engine">
<cx:client-reference setter="setEngine"/>
</cx:reference>
<cx:reference name="steeringWheel" structural-relationship="child"
component-types="car:SteeringWheel">
<cx:client-reference field="steeringWheel"/>
</cx:reference>
<cx:reference name="owner" structural-relationship="parent"
component-types="car:Owner">
<cx:client-reference property="owner"/>
</cx:reference>
</cx:component-class>
<!--
! Engine class
!
! An abstract class (no JavaScript implementation), that defines an API that other engines
! implementations have to follow. At the moment there is no way within the component class
! to define the API (in terms of methods, parameters, etc. However, future enhancements
! may add that support.
!-->
<cx:component-class name="car:Engine" defines="reference-types"/>
<!--
! Diesel Engine class
!
! A concrete implementation of an engine.
!-->
<cx:component-class name="car:DieselEngine" implements="car:Engine"
defines="reference-types" id="Diesel">
<mcs:script type="text/javascript">
// ==================================================
// JavaScript representation of Diesel Engine
// ==================================================
var DieselEngine = V$.Class.create();
DieselEngine.methods({
initialize: function(name) {
this.name = name;
}
});
</mcs:script>
</cx:component-class>
<!--
! Steering Wheel Class
!
! A concrete implementation of a steering wheel.
!-->
<cx:component-class name="car:SteeringWheel" defines="reference-types" id="SteeringWheel">
<mcs:script type="text/javascript">
// ==================================================
// JavaScript representation of Steering Wheel
// ==================================================
var SteeringWheel = V$.Class.create();
SteeringWheel.methods({
initialize: function(color, texture) {
this.color = color;
this.texture = texture;
}
});
</mcs:script>
</cx:component-class>
<!--
! Car Owner Class
!
! A concrete implementation of a car owner.
!-->
<cx:component-class name="car:Owner" defines="reference-types">
<mcs:script type="text/javascript">
// ==================================================
// JavaScript implementation of Owner
// ==================================================
var Owner = V$.Class.create();
Owner.mixin(V$.cf2.OPSet);
V$.cf2.OPProto.add(Owner, "car", {});
V$.cf2.OPProto.add(Owner, "name", {});
Owner.methods({
initialize: function(name) {
V$.cf2.OPProto.init(this);
this.setName(name);
}
});
</mcs:script>
</cx:component-class>
</head>
<body>
<!--
! Steering Wheel Instance
!-->
<cx:component component-id="PinkFluffySteering" class-ref="SteeringWheel">
<!--
! Must specify in each component instance how to construct a steering wheel as no
! name was specified on the cx:client-class element.
!-->
<cx:client-create>
return new SteeringWheel("pink", "fluffy");
</cx:client-create>
</cx:component>
<!--
! Engine instance
!-->
<cx:component component-id="Zetec" class-ref="Diesel">
<!--
! Must specify in each component instance how to construct a diesel engine as no
! name was specified on the cx:client-class element.
!-->
<cx:client-create>
return new DieselEngine("zetec");
</cx:client-create>
</cx:component>
<!--
! Poor refers to how much money the driver has to spend on cars, not the quality of
! their driving. A poor driver is one that can only have at most a single car.
!-->
<cx:component id="PoorDriver">
<!--
! An anonymous (no name, no id) class.
!
! The encapsulating component does not specify class-ref attribute so it is bound to
! this class.
!-->
<cx:component-class defines="reference-types" implements="car:Owner">
<!--
! Car references are optional - due to min-occurs = 0
!-->
<cx:reference name="car" structural-relationship="child" component-types="car:Car"
min-occurs="0">
<cx:client-reference setter="setCar"/>
</cx:reference>
</cx:component-class>
<cx:client-create>
// Create the client side representation of a poor driver.
return new Owner("Willy");
</cx:client-create>
<cx:component id="CompanyCar" class-ref="Car">
<cx:ref name="engine" component="Zetec"/>
<cx:ref name="steeringWheel" component="PinkFluffySteering"/>
<!--
! Owner is implicitly bound to containing component.
!-->
<cx:client-start>
// Make sure that the car owner name is appropriate to the steering wheel.
c.setOwner('Shirley');
</cx:client-start>
</cx:component>
</cx:component>
<div>
<cst:value property-value="PoorDriver#name" path="."/> has a
<cst:value property-value="PoorDriver#car" path="steeringWheel.color"/>
<cst:value property-value="PoorDriver#car" path="steeringWheel.texture"/>
steering wheel and a <cst:value property-value="PoorDriver#car" path="engine.name"/> engine!
<div>
<ui:button>
<cf2:on event="cf2:activate" set="PoorDriver#name" string="Fred"/>
Rename to Fred
</ui:button>
</div>
</div>
</body>
</html>