As of Echo3-Beta8 this sample does no longer work, since the event model for text components has been modified.
This example is still here in the wiki.
If you want to create an "auto suggest"-formular or a search-form which immediately shows the results (without hitting enter or submit), you'll need a TextField which synchronizes each keystroke between client and server. The Echo3-default-TextField synchronizes only when enter is hit to avoid unnecessary load. This is a very good default. But there are special corner-cases where do you want more interaction. Please use the following component with care, it creates a request for every keystroke per default. You can configure a keystrokeDelay which synchronizes only after the specified delay. So someone who is typing rapidly doesn't create lots of requests, instead the synchronization is triggered only when the user stops typing for the specified amount of time (=keystrokeDelay). Below is an example which should guide you through the process of creating an Echo3-component.
How to create the component for Echo3?
To use the following component, please create two packages jfix.echo and jfix.echo.resource in your project and store the files below as mentioned within these packages (exception is the last file, which needs to go into a special folder /META-INF/... of your classpath).
Please note: Depending on your IDE / build-process, ALL files mentioned below must end up in directories below /WEB-INF/classes/.
1. Create a Java-Component (/jfix/echo/KeystrokeTextField.java)
package jfix.echo;
import nextapp.echo.app.text.Document;
public class KeystrokeTextField extends nextapp.echo.app.TextField {
public static final String PROPERTY_KEYSTROKE_DELAY = "keystrokeDelay";
public KeystrokeTextField() {
super();
}
public KeystrokeTextField(Document document, String text, int columns) {
super(document, text, columns);
}
public KeystrokeTextField(Document document) {
super(document);
}
/**
* Sets the delay before a keystroke is synchronized between client and server.
*
* @param newValue delay in milliseconds
*/
public void setKeystrokeDelay(int newValue) {
set(PROPERTY_KEYSTROKE_DELAY, newValue);
}
}
2. Create a server-side-peer for the component, which synchronizes the state of the component to the client-side (/jfix/echo/KeystrokeTextFieldPeer.java)
package jfix.echo;
import nextapp.echo.app.Component;
import nextapp.echo.app.util.Context;
import nextapp.echo.webcontainer.ServerMessage;
import nextapp.echo.webcontainer.WebContainerServlet;
import nextapp.echo.webcontainer.service.JavaScriptService;
public class KeystrokeTextFieldPeer extends nextapp.echo.webcontainer.sync.component.TextFieldPeer {
private static final String JFIX_KEYSTROKE_TEXT_FIELD = "JFixKeystrokeTextField";
static {
WebContainerServlet.getServiceRegistry().add(
JavaScriptService.forResource(JFIX_KEYSTROKE_TEXT_FIELD,
"jfix/echo/resource/KeystrokeTextField.js"));
}
public void init(Context context, Component component) {
super.init(context, component);
ServerMessage serverMessage = (ServerMessage) context
.get(ServerMessage.class);
serverMessage.addLibrary(JFIX_KEYSTROKE_TEXT_FIELD);
}
public Class getComponentClass() {
return KeystrokeTextField.class;
}
public String getClientComponentType(boolean shortType) {
return JFIX_KEYSTROKE_TEXT_FIELD;
}
}
3. Create a client-side representation of the component (/jfix/echo/resource/KeystrokeTextField.js)
if (!Core.get(window, ["JFix", "Sync"])) {
Core.set(window, ["JFix", "Sync"], {});
}
JFix.KeystrokeTextField = Core.extend(Echo.TextField, {
$load : function() {
Echo.ComponentFactory.registerType("JFixKeystrokeTextField",
this);
},
componentType : "JFixKeystrokeTextField"
});
JFix.Sync.KeystrokeTextField = Core.extend(Echo.Sync.TextField, {
$load : function() {
Echo.Render.registerPeer("JFixKeystrokeTextField", this);
},
$construct : function() {
Echo.Sync.TextField.call(this);
// Here we apply a "monkey-patch" for a private method...
// This is a bad practice in general, but this way we can reuse
// lots of existing code.
// Hopefully someday _processKeyUp (or the relevant part of if)
// will be a virtual method...
this._textFieldProcessKeyUp = this._processKeyUp;
this._processKeyUp = this.processKeyUp;
},
processKeyUp : function(e) {
this._textFieldProcessKeyUp(e);
// keyCode == 13 is handled by default keyUp-Handler above.
if (e.keyCode != 13) {
var keystrokeDelay = this.component.render(
"keystrokeDelay", 0);
if (keystrokeDelay == 0) {
this.executeSync();
} else if (keystrokeDelay > 0) {
this.scheduleSync(keystrokeDelay);
}
}
return true;
},
scheduleSync : function(delay) {
// Cancel pending sync before a new sync is scheduled,
// so only the last sync in a row is executed
// while someone is typing rapidly.
this.cancelPendingSync();
this.pendingSync = Core.Web.Scheduler.run(Core.method(this,
this.executeSync), delay, false);
},
cancelPendingSync : function() {
if (this.pendingSync !== undefined) {
Core.Web.Scheduler.remove(this.pendingSync);
delete this.pendingSync;
}
},
executeSync : function() {
var newText = this.component.get("text");
if (this.oldText != newText) {
this.oldText = newText;
this.component.doAction();
}
}
});
4. Create mapping between component and peer (/META-INF/nextapp/echo/SynchronizePeerBindings.properties)
jfix.echo.KeystrokeTextField jfix.echo.KeystrokeTextFieldPeer
5. Now you can use the KeystrokeTextField-component in your application like any other component, e.g. create some kind of typewriter
final Label label = new Label();
final KeystrokeTextField textfield = new KeystrokeTextField();
textfield.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
label.setText(textfield.getText().toUpperCase());
}
});