Extending with custom controls
From DaamWiki
Contents |
Extending with custom controls
If you want to create custom controls built from existing basic controls, just go on, extend any Container or Panel and add your own stuff. You might also extend basic fields like TextField to add custom behaviour.
The tricky part comes when you need more than this. For example if you want to create a special text input field, which validates the input string with a specific pattern as the user types the text, you'll need client side javascript code (nb this kind of component will exist in the framework at some point). The Daam client side stub is developed using GWT. Before starting to develop a custom control, please get familiar with GWT, it's fun and easy.
Creating the projects
As you will develop the custom control in GWT, it has to be compiled together with the existing UI framework code. And as built GWT code is loaded from the daam.jar, you'll also have to build your version of it (nb you can also include your built stuff directly in the WAR file and get rid of the existing stuff in daam.jar, so that the classloader will locate the files in the WAR root). To get started, just import the projects "daam", "DaamWeb", "CustomControl" and "CustomControlTest" to your development environment (I use Eclipse 3.4), and resolve classpath problems so that all projects build properly.
CustomControl is the project that implements the browser side of your custom controls. You can as well get started with a GWT 1.5 generated application. The GWT application should inherit the code from DaamStub:
<module>
<inherits name='daam.ui.gwt.DaamStubNoEntryPoint' />
<entry-point class='custom.client.CustomStub' />
<stylesheet src='CustomStub.css' />
</module>
You'll have to define a ComponentFactory which will be responsible of creating the controls based on their type identifier.
public class CustomComponentFactory extends ComponentFactory {
static {
componentFactories.add(new CustomComponentFactory());
}
public IControl getControl(String controlName) {
if ("custom.mylabel".equals(controlName)) return new IMyLabel();
return null;
}
}
The static initializer registers this ComponentFactory instance to the Stub, thus ensures that this factory gets invoked.
The GWT EntryPoint implementation should extend the DaamStub as follows:
public class CustomStub extends DaamStub {
public void initComponentFactories() {
super.initComponentFactories();
new CustomComponentFactory();
}
}
It should override only this one method, and ensure that the class CustomComponentFactory gets loaded.
And the real content will be the integration component. In this first example we create a control with actually no communication with the server:
public class IMyLabel extends IControl {
Label label;
protected Widget initWidget(JSONObject attributes) {
label = new Label();
return label;
}
protected void init(JSONObject attributes) {
label.setStyleName("h2");
label.setText("Hello world");
}
}
The initWidget method usually just creates a Widget instance. A Label typed variable is declared to have a typed instance of the created widget. The attributes parameter is passed because the instantiation might also rely on information coming from the server. Normally, the init method is responsible of initializing widget attributes. You might also take a brave look into IControl implementation to see how it actually works.
The server side is in the CustomControlTest project.
public class MyLabel extends Control {
@Override
public String getElementName() {
return "custom.mylabel";
}
}
A control implementation must implement the above method.
The application using custom controls have a little bit different descriptors and build. The servlet must be initiated according to the new entry point page:
<servlet>
<servlet-name>daamservlet</servlet-name>
<servlet-class>daam.DaamServlet</servlet-class>
<init-param>
<param-name>rootContainer</param-name>
<param-value>customPanel</param-value>
</init-param>
<init-param>
<param-name>entryPointPage</param-name>
<param-value>CustomStub.html</param-value>
</init-param>
</servlet>
And the www directory of the CustomControl project, built by GWT, must be included in the WAR root:
<copy todir="${wardist}">
<fileset dir="../CustomControl/www/custom.CustomStub" includes="**"/>
</copy>
And that's all to get started.
Integration
The control created above is more than simple. You'll surely need to pass parameters from the server side to the browser side, you'll have to create events going from browser side to server side and back. You're highly encouraged to look into existing control code to find patterns to follow. Nevertheless, I'll here try to enumerate the basic tasks you'll face.
Rendered parameters
Let's take a look at the paintContent method of Label:
@Override
public void paintCustomContent() throws JSONException {
fireUpdate("text", text);
}
When rendering a control, you register the property updates of the properties you want to be part of the rendered description. The property names are Strings and the property values can be Strings, or any JSONValue objects. The browser and client side stubs use JSON to serialize information. If you're not familiar with JSON, take a look at http://www.json.org/. A control is usually rendered fully only once, when first passed to the browser side. Later on, only update events are passed. In the code above, String typed parameters are passed. These information are used in the ILabel browser side integration class.
public void receiveUpdateEvent(String propertyName, Object newValue) {
super.receiveUpdateEvent(propertyName, newValue);
if ("text".equals(propertyName)) {
label.setText((String) newValue);
}
}
Width, height and styleClass attributes are automatically applied on all controls (see Control and IControl implementation), so you just have to care about specific ones.
Note that in the server side paint code other JSONValues can be passed as well. ListBoxes for example use JSONArray-s to pass their items. But as seen above, java.lang.String instances automatically get converted into JSONString-s.
Server side events
When changing an attribute of a server side control, the client side should be informed as well. To achieve this, we create setter methods like this:
public void setText(String text) {
this.text = text;
fireUpdate("text", text);
}
The fireUpdate call will enqueue an update event in the event queue. The event queue belongs to a HTTP request. As normally UI code is invoked only on user interaction, which creates an AJAX HTTP request. The response of this request will contain the events which were enqueued during the request processing. Note that this way the framework won't automatically send events from server side without user interaction, which might be useful in case of an incoming JMS message, or when implementing progress bars with monitor threads. This feature will be added in a later version.
The browser side ILabel class takes care of updating the GWT widget itself:
public void receiveUpdateEvent(String propertyName, Object newValue) {
super.receiveUpdateEvent(propertyName, newValue);
if ("text".equals(propertyName)) {
label.setText((String) newValue);
}
}
Note that when receiving update events, JSONString-s are automatically converted into Strings, you don't have to do boxing as in case of rendering. You might also note that this code above is the same mention in the caption about rendering. This is because normally the same operation has to be performed with a property when first rendering a component and when an update event comes for that property (that is, you'll call Label.setText in both cases). For easier implementation, the IControl implementation fires update events for the rendered properties, so that the IControl descendant can handle the two cases with the same code. The IControl.init() method can be overriden to perform initialization specific operations - usually handling properties which cannot be updated once the control is rendered.
Client side events
So what happens when the user clicks a button in the browser? Let's take a look at IButton implementation:
protected void init(JSONObject attributes) {
button.addClickListener(new ClickListener() {
public void onClick(com.google.gwt.user.client.ui.Widget sender) {
sendEvent("click", "");
};
});
}
An event is sent to the server side using the sendEvent method. The method "sendEvent" is implemented in IControl. It creates an AJAX request immediately. The server side Button class handles the event:
@Override
public void receiveEvent(String event, Object data) {
if ("click".equals(event)) {
if (clickListener != null) {
clickListener.buttonClicked(this);
}
if (delegate != null) {
delegate.invoke();
}
}
}
In most cases, for example when the text value of an input field changes, you'll not always want to notify the server immediately, only when a button is clicked for example. You can use the "registerEvent" method on the client side to put your event in the queue that will be send to the server side stub when someone calls a "sendEvent". Controls extending AbstractField automatically support the "immediate" property to decide if events have to be sent immediately or just have to be queued.
