CaptainCasa Enterprise Clients provides the possibility to enhance the set of existing components in a flexible way.
We differentiate between two types of components:
“Composite Components” - These are components in which you arrange one or more existing components to form some “higher value component”, which not only contains some defined graphical arrangement of existing components but also contains some program logic. Examples are:
A component managing a COMBOFIELD that is automatically bound to some database processing.
An address component, in which an address is input.
A generic grid component, showing lists of business objects.
“Client Components” - These are components that are available on (JavaScript-) client side and which can be controlled from server side. Example are:
...all the base components that CaptainCasa provides: field, button, slider...
The “Composite Components” have no implication on client side – they are server-side only. The way to implement is the creation of “Page Bean Components”. Page bean components are defined as normal dialogs – having a layout definition with code that is bound.
Page bean components are directly supported in the Layout Editor by using the component PAGEBEANCOMPONENT.
Please check the Developer's Guide for more information on page bean components.
The client side of CaptainCasa is a rendering engine that is written in JavaScript. The rendering engine uses a component library that is developed by CaptainCasa following the so called “RISC-method”.
Bringing in new client components means that you really add some new optical components to the JavaScript client – and only is done, if there is no existing component provided inside the CaptainCasa control library.
This is the central topic of this documentation.
Create a just normal CaptainCasa project.
Nothing special here... - just create a project in the way you prefer: directly through toolset, via Maven, ...
In the CaptainCasa tools: start function “Create control library extension...” from the project menu:
In the dialog that shows up you need to define a prefix for your components and a package in which your component implementation is placed. Example:
After pressing “Create” some files will be generated within your project. Let's take a look onto the files:
“cccontrollibrary.xml” - this is the “most important” file. It is always placed in the root package of your sources and contains the basic information about the library itself and about the controls that are contained.
“xml files in resources-package” - there are 4 XML files that add further definitions to your component – mainly used for properly integrating the component in to the Layout Editor.
“extension.css” and “extension.js” - here you place the JavaScript code and additional CSS definitions
“YOURCOMPONENTComponentTag.java” and “YOURCOMPONENTComponent.java” - each component of your library requieres an implementation of these to classes. The default implementation is to just copy the “YOURCOMPONENT*”-classes and just rename them to match your component.
If your project was created through the CaptainCasa toolset (and not by Maven) then you typically have to add a “jsp-api.jar” library to the classpath of the project within your IDE (e.g. Eclipse), so that the IDE is able to resolve all classes.
Now the component library is “ready to go”! There are no components inside yet... - so the next chapter explains how to add components.
The graphical component is a quite simple one – at the beginning. A rectangle area on the screen that can be colored.
The name of the component is “coloredrect” - so together with the prefix of the component library, that was created in the previous chapter, it is “test:coloredrect”.
The core definition is done in “cccomponentlibrary.xml”. Here we add the following definition:
<?xml version="1.0" encoding="UTF-8"?>
<taglib prefix="test"
clientprefix="test"
packagename="some.test">
<tag>
<name>coloredrect</name>
<attribute><name>id</name></attribute>
<attribute><name>rectcolor</name></attribute>
<attribute><name>height</name></attribute>
<attribute><name>width</name></attribute>
</tag>
</taglib>
Please follow the rules:
The name of the component must be lowercase only! Do not use any “strange” characters, do not add spaces, columns, commas, dots...
The attributes must contain the “id”-attribute!
In “controlattributeusage.xml” you need to define meta information for the component attributes.
<controlattributeusage>
<tag name="test:coloredrect">
<!-- mandatory attributes -->
<attribute name="background"/>
</tag>
</controlattributeusage>
The most important information is to define the mandatory attributes. This information is used within the Layout Editor of CaptainCasa in order to indicate to the user, where a component instance is not properly configured.
In “controlfavoriteattributes” you may define these attributes which show up as “important” attributes in the Layout Editor within the attributes-area.
<controlfavoriteattributes>
<tag name="test:coloredrect">
<attribute name="background"/>
</tag>
</controlfavoriteattributes>
The Layout Editor needs to know where your component can be placed within a layout definition – so that the user can pick it via the right mouse button menu that is available in the tree of componets.
The corresponding definition is done in “controlsarrangement.xml”:
<controlsarrangement>
<tag name="t:row" below="test:coloredrect"/>
<tag name="test:coloredrect" folder="Perfect test components"/>
</controlsarrangement>
The attribute “rectcolor” of the new component is not contained in the default attribute names that are used by CaptainCasa. So you may add further information, which is showing up in the Layout Editor when editing the attribute.
The definition in “controlattributeinfo.xml” is:
<controlattributeinfo>
<attribute name="rectcolor" text="The background color of the rect-area.">
<attributevalue value="#FF0000" text="Red"/>
<attributevalue value="#00FF00" text="Green"/>
<attributevalue value="#0000FF" text="Blue"/>
</attribute>
</controlattributeinfo>
For each component you need to define two classes – one “ComponentTag”-class and one “Component”-class. You can just copy the existing “YOURCOMPONENTComponent”-class and the exsiting “YOURCOMPONENTComponentTag”-class.
The name of your classes must exactly follow the pattern:
<ComponentNameInUpperCase>Component
<ComponentNameInUpperCase>ComponentTag
package some.test;
import org.eclnt.jsfserver.elements.BaseActionComponent;
public class COLOREDRECTComponent
extends BaseActionComponent
{
}
package some.test;
import org.eclnt.jsfserver.elements.BaseComponentTag;
public class COLOREDRECTComponentTag
extends BaseComponentTag
{
public void setRectcolor(String value)
{
m_attributes.put("rectcolor",value);
}
}
The “ComponentTag” class requires a set-method for each attribute of the component. Because it is extended from “BaseComponentTag”, all the existing CaptainCasa attribute names are already inherited – so you do not have to take care of the attributes “id”, “widht”, “height”. But the attribute “rectcolor” is not part of CaptainCasa attributes, so you have to add it.
After building your project, reloading your application in the Layout Editor and refreshing the Layout Editor, you now can see the new component being part of the editing environment:
You can edit the component attributes...
...but you do not see any component in the preview area of the page right now – because there is still some client implementation missing!
The JavaScript is by default placed into the “extension.js” file. This file is automatically loaded by the browser if a project is using your component library.
The JavaScript code requires you to write two classes (functions):
One class is the component class, that is adding a new component to the client-side. This class in principal is representing a stand-alone component, which may not be connected to the server-side CaptainCasa processing at all.
The other class is the element class, which is the representation of the XML layout data that is sent from the server side to the client side.
/*
* Component implementation.
*/
function TESTCOLOREDRECTControl(asPrototype)
{
this.init_TESTCOLOREDRECTControl = function()
{
this.init_RISCComponent();
this.setStyleClass("riscpane");
this.setBackgroundColor("#0000FF");
};
this.calculateMinimumSize = function()
{
return new RISCDimension(50,50);
};
this.setRectColor = function(value)
{
this.setBackgroundColor(value);
};
this.layoutChildren = function(left,top,width,height)
{
};
// -------------------------------------------------------------------------
if (asPrototype != true) this.init_TESTCOLOREDRECTControl();
}
TESTCOLOREDRECTControl.prototype = new RISCComponent(true);
/*
* Element implementation.
*/
function TESTCOLOREDRECTElement(asPrototype)
{
this.init_TESTCOLOREDRECTElement = function()
{
this.init_CCControl();
this.applyComponentData = this.applyComponentData_TESTCOLOREDRECTElement;
this.m_ccClassName = "TESTCOLOREDRECTElement";
// --------------------------------------------------------------------
var vControl = new TESTCOLOREDRECTControl();
this.bindUI5Node(vControl);
};
this.applyComponentData_TESTCOLOREDRECTElement = function()
{
this.applyComponentData_CCControl();
if (this.m_attributesChanged["rectcolor"] == true)
{
this.m_ui5Node.setRectColor(this.m_attributes["text"]);
}
};
if (asPrototype != true) this.init_TESTCOLOREDRECTElement();
}
TESTCOLOREDRECTElement.prototype = new CCControl(true);
s_CCPageElement_ElementCreators["test_coloredrect"] = function() { return new TESTCOLOREDRECTElement(); };
Please always remember:
When editing the JavaScript code you are working in the project. You need to hot-deploy or reload to copy it over into the runtime system.
In the brwoser use “ctrl-F5” to reload, because this clears the cache.
Finally the result looks as follows:
The component is rendered and is following the configuration in the layout definition.
There is an API documentation of the client side components. Please open the following link:
http://captaincasa.com/docu/api_clientjs
The source code of the client side is available through a public GIT repository. Please contact CaptainCasa (info@CaptainCasa.com) in order to obtain access.
For each client-side component you need to develop two classes. (We talk here about “classes” - actually these are JavaScript “functions” - but used in the meaning of classes.)
The so called “Element” class
The “Component” class itself.
The server-side of CaptainCasa sends an XML layout description to the client-side JavaScript processing. This layout description is very similar to the XML layout definitions (i.e. the XML inisde the .jsp pages) – but considers certain runtime aspects:
Only changes inside the XML are sent. - The server-side knows what it already has sent to the client-side – if a layout does not change due to server-side application processing, then it is not required to resend the whole layout – it's enough to send only this part of the XML which changed.
Some components are resolved on server-side: some of the components (e.g. ROWBODYPANE) internally resolve into several sub-components (e.g. a ROW/SCROLLPANE-combination) on server-side. In the runtime-XML you do not see the original component (ROWBODYPANE) but the resolved ones.
The prefixes of the server-side are not considered for the CaptainCasa components – a “t:pane” component in the layout is transferred as “pane” in the runtime XML. - For non-CaptainCasa components, the prefix is prepended with a “_”-separator. Example: The “test:coloredrect” component is transferred as “test_coloredrect”.
On client side the XML layout description is transferred into an hierarchy of “Element” objects – each element representing one element of the XML.
The purpose of the element objects is to receive the attributes from the XML data, to create a UI-component out of it and to transfer changing attribute values to the component. Vice versa the element object receives events from its UI-component and transfers changed data and event data back to the server-side processin.
The element object as consequence is the translator between what happens on the XML communication side and the actual graphical components.
Let's take a look onto the implementation of the “test:coloredrect” example...
function TESTCOLOREDRECTElement(asPrototype)
{
this.init_TESTCOLOREDRECTElement = function()
{
this.init_CCControl();
this.applyComponentData = this.applyComponentData_TESTCOLOREDRECTElement;
this.m_ccClassName = "TESTCOLOREDRECTElement";
// --------------------------------------------------------------------
var vControl = new TESTCOLOREDRECTControl();
this.bindUI5Node(vControl);
};
this.applyComponentData_TESTCOLOREDRECTElement = function()
{
this.applyComponentData_CCControl();
if (this.m_attributesChanged["rectcolor"] == true)
{
this.m_ui5Node.setRectColor(this.m_attributes["text"]);
}
};
if (asPrototype != true) this.init_TESTCOLOREDRECTElement();
}
TESTCOLOREDRECTElement.prototype = new CCControl(true);
s_CCPageElement_ElementCreators["test_coloredrect"] = function() { return new TESTCOLOREDRECTElement(); };
...and let's walk through step by step:
function TESTCOLOREDRECTElement(asPrototype)
{
...
}
TESTCOLOREDRECTElement.prototype = new CCControl(true);
The name of the class is “TESTCOLOREDRECTElement” - which is the concatenation of the prefix, the name of the component and the word “Element”. We strongly recommend to follow this naming convention.
The class is taking over all content from the “CCControl” class – which serves as its prototype.
...
s_CCPageElement_ElementCreators["test_coloredrect"] = function() { return new TESTCOLOREDRECTElement(); };
A function to create an instance of this class is registered. This means: if layout-XML is received on the client side and if the XML contains an element “test_coloredrect”, then the corresponding element instance is created by the function that is passed into the global object “s_CCPageElement_ElementCreators”.
Remember that the server-side prefix is prepended with “_” when the control is communicated to the client-side.
function TESTCOLOREDRECTElement(asPrototype)
{
this.init_TESTCOLOREDRECTElement = function()
{
...
};
if (asPrototype != true) this.init_TESTCOLOREDRECTElement();
}
If the class/function “TESTCOLOREDRECTElement” is called, and if it is not called as prototyp function, then the init-method of the class is called. The init-method is implemented with the name “init_<celementClassName>” so that it does not conflict with prototype implementations.
this.init_TESTCOLOREDRECTElement = function()
{
this.init_CCControl();
this.applyComponentData = this.applyComponentData_TESTCOLOREDRECTElement;
...
};
this.applyComponentData_TESTCOLOREDRECTElement = function()
{
this.applyComponentData_CCControl();
...
};
In the init-method first the init of the parent/prototype-class is called.
The element class needs to implement three central methods:
applyComponentData
addChildNode (if there are child components)
removeChildNode (if there are child components)
You see the function “applyComponentData” is explicitly assigned with the function “applyComponentData_<elementClassName>”. In the implementation of the function “applyComponentData_TESTCOLOREDRECTElement” first the implementation of the parent/prototype class is called.
If the class also manages sub-components, then the same meachnism has to be done for the methods “addChildNode” and “removeChildNode” - example:
this.init_TESTCOLOREDRECTElement = function()
{
this.init_CCControl();
...
this.addChildNode = this.addChildNode_TESTCOLOREDRECTElement;
this.removeChildNode = this.removeChildNode_TESTCOLOREDRECTElement;
...
};
this.addChildNode_TESTCOLOREDRECTElement = function(ccControl,index)
{
this.addChildNode_CCControl(ccControl,index);
...
};
this.removeChildNode_TESTCOLOREDRECTElement = function(ccControl)
{
this.removeChildNode_CCControl(ccControl);
...
};
In our example of the “test:coloredrect” component we do not manages sub-components, to we do not have to to do so.
this.init_TESTCOLOREDRECTElement = function()
{
...
var vControl = new TESTCOLOREDRECTControl();
this.bindUI5Node(vControl);
};
this.applyComponentData_TESTCOLOREDRECTElement = function()
{
...
if (this.m_attributesChanged["rectcolor"] == true)
{
this.getUI5Node().setRectColor(this.m_attributes["text"]);
}
};
In the “init_” method, finally the graphical component is created and passed into the internal processing via the “bindUI5Node(...)” method.
In the “applyComponentData” implementation, the attributes of the element are checked for changes and changed values are transferred to the component. The method “applyComponentData” is called when the element shows up first, and when there are changes to its attributes due to server-side updates.
The component class is representing a visible component. It uses the CaptainCasa RISC library for building up corresponding DOM elements in the browser. This CaptainCasa RISC library is a just normal library of classes – in which at runtime component instances are arranged as tree.
The component library in principal is completely separated from the XML/Element-processing that is described in the previous chapter!
Let's again use the “test:coloredrect” example...
function TESTCOLOREDRECTControl(asPrototype)
{
this.init_TESTCOLOREDRECTControl = function()
{
this.init_RISCComponent();
this.setStyleClass("riscpane");
this.setBackgroundColor("#0000FF");
};
this.calculateMinimumSize = function()
{
return new RISCDimension(50,50);
};
this.setRectColor = function(value)
{
this.setBackgroundColor(value);
};
this.layoutChildren = function(left,top,width,height)
{
};
// -------------------------------------------------------------------------
if (asPrototype != true) this.init_TESTCOLOREDRECTControl();
}
TESTCOLOREDRECTControl.prototype = new RISCComponent(true);
...and walk through the significant parts:
function TESTCOLOREDRECTControl(asPrototype)
{
this.init_TESTCOLOREDRECTControl = function()
{
...
};
if (asPrototype != true) this.init_TESTCOLOREDRECTControl();
}
TESTCOLOREDRECTControl.prototype = new RISCComponent(true);
The class is named “TESTCOLOREDRECTControl” following the convention: “prefix” + “component name” + “Control”. We strongly recommend to follow this convention.
The class takes over all processing from the class “RISCComponent”, which serves as prototype. An init_* method serves as “constructor”. RISCComponent is the most generic control class – providing a rectangular area that can be modified by a lot of properties, and that may have sub-components that are managed inside. (This feature is not used in this example.)
this.calculateMinimumSize = function()
{
return new RISCDimension(50,50);
};
Each component is part of a component tree, inside the component tree there are components (PANE, ROW, ...) that take over the arrangement of sub-components. To do so, these components have to know the minimum size of the sub-components.
this.setRectColor = function(value)
{
this.setBackgroundColor(value);
};
The component provides some property “rectColor” - in this case the property directly calls a property that is implemented by its parent/prototype class.
Let's extend the example “test:coloredrect” and extend it in the following way:
If the user clicks the component, then a corresponding event should be triggered on srver-side.
This time we start with the component class – because this is the one that actually receives the event out of the browser's DOM processing. The code is extended by the highlighted lines:
function TESTCOLOREDRECTControl(asPrototype)
{
// keep instance of prototype implementation's method
this.reactOnRISCEvent_parent_TESTCOLOREDRECTControl = this.reactOnRISCEvent;
this.init_TESTCOLOREDRECTControl = function()
{
this.init_RISCComponent();
this.m_coloredRectListeners = [];
this.setStyleClass("riscpane");
this.setBackgroundColor("#0000FF");
// register for click events => events are passe to the method
// reactOnRISCEvent
this.setClickCallback(this);
};
this.calculateMinimumSize = function()
{
return new RISCDimension(50,50);
};
this.setRectColor = function(value)
{
this.setBackgroundColor(value);
};
this.layoutChildren = function(left,top,width,height)
{
};
this.addColoredRectListener = function(listener)
{
this.m_coloredRectListeners.push(listener);
};
this.reactOnRISCEvent = function(riscEvent)
{
var parentResult = this.reactOnRISCEvent_parent_TESTCOLOREDRECTControl(riscEvent);
if (parentResult == true)
return true;
if (riscEvent.type == RISCEVENTTYPE_onclick)
{
this._processClickedEvent();
return true; // no further processing, no bubbling
}
};
this._processClickedEvent = function(riscEvent)
{
for (var i=0; i<this.m_coloredRectListeners.length; i++)
this.m_coloredRectListeners[i]("rectSelected",riscEvent);
};
// -------------------------------------------------------------------------
if (asPrototype != true) this.init_TESTCOLOREDRECTControl();
}
TESTCOLOREDRECTControl.prototype = new RISCComponent(true);
In the constructor the control registers for click-callbacks.
The call-backs are done into method “reactOnRISCEvent”. This method is available on prototyp level already, so you need to park the method (this.reactOnRISCEvent_parent_TESTCOLOREDRECTControl) and call the parked method in the own implementation.
Because the component should be generically usable, it itself provides a listener registration and a processing of the listeners in case a click is executed.
The element class is the one to bind the graphical component to the server side processing. So it's the one to listen to the component's event and to transfer these events which are relevant.
The code now looks as follows:
function TESTCOLOREDRECTElement(asPrototype)
{
this.init_TESTCOLOREDRECTElement = function()
{
this.init_CCControl();
this.applyComponentData = this.applyComponentData_TESTCOLOREDRECTElement;
this.m_ccClassName = "TESTCOLOREDRECTElement";
// --------------------------------------------------------------------
var vControl = new TESTCOLOREDRECTControl();
var that = this;
vControl.addColoredRectListener(function(name,riscEvent) {that._processColoredRectEvent(name,riscEvent);});
this.bindUI5Node(vControl);
};
this.applyComponentData_TESTCOLOREDRECTElement = function()
{
this.applyComponentData_CCControl();
if (this.m_attributesChanged["rectcolor"] == true)
{
this.m_ui5Node.setRectColor(this.m_attributes["text"]);
}
};
this._processColoredRectEvent = function(name,riscEvent)
{
if ("rectSelected" == name)
{
// send action to server side
var command = "rectSelected()";
this.getPage().callServer(this,this.getId()+".action",command);
}
};
if (asPrototype != true) this.init_TESTCOLOREDRECTElement();
}
TESTCOLOREDRECTElement.prototype = new CCControl(true);
s_CCPageElement_ElementCreators["demo_coloredrect"] = function() { return new TESTCOLOREDRECTElement(); };
In the “init_*” method the element creates the component – and registers for the events. Please make sure that in the function that is called by the event, there is a “that”-processing – and not a “this”-processing!
In the actual method that is called by the event-processing, the function “this.getPage().callServer(...)” is invoked. Therer are two parameters
An id that is passed with the event – this by default is “this.getId()+”.action””.
A command – the command is a String following the syntax “commandName(param1,param2,...)”. In the example there are no parameters, so it is simply “rectSelected()”.
Now that your component sends out events, you need to update the configuration of attributes in “cccomponentlibrar.xml” by adding the “actionListener” attribute:
<tag>
<name>coloredrect</name>
<attribute><name>id</name></attribute>
<attribute><name>background</name></attribute>
<attribute><name>height</name></attribute>
<attribute><name>width</name></attribute>
<attribute><name>actionListener</name></attribute>
</tag>
The event of the client triggers a round-trip to the server-side. On server-side the event command is transferred into a Java-event object. There are two possibilities:
If there is a special event-class within the component libraries package with the name “BaseActionEvent<NameOfCommand>”, then an instance of this class will be created and passed.
It there is no specail event-class then a normal BaseActionEvent will be created and passed.
The special event class for our example looks like:
package democontrols;
import javax.faces.component.UIComponent;
import org.eclnt.jsfserver.elements.BaseActionEvent;
public class BaseActionEventRectSelected
extends BaseActionEvent
{
public BaseActionEventRectSelected(UIComponent component, String type)
{
super(component, type);
}
}
The constructor must exactly provide the two parameters – otherwise the object will not be created.
Now you can bind the actionListener of the component to some Java-method – just as with “normal” controls. The event that will be passed in the actionListener-method will be of type “BaseActionEventRectSelcted”.