I'm trying to have an handler on Text Area(gxt) to get to know when user reach the top of text area.
TextArea logTextArea = new TextArea();
logTextArea.setReadOnly(true);
logTextArea.addDomHandler(new ScrollHandler() {
#Override
public void onScroll(ScrollEvent event) {
InputElement textAreaElement = logTextArea.getCell().getInputElement(logTextArea.getElement());
int scrollTop = textAreaElement.getScrollTop();
}
}, ScrollEvent.getType());
VerticalLayoutContainer dataContainer = new VerticalLayoutContainer();
HorizontalLayoutContainer secondRow = new HorizontalLayoutContainer();
secondRow.add(logTextArea, new HorizontalLayoutData(1, 1, new Margins(5, 10, 5, 10)));
dataContainer.add(secondRow, new VerticalLayoutData(1, 0.5));
add(dataContainer);//this class extends ContentPanel
This handler is never called on scroll, but I also tried with a lot of other events, like mouseover, mousewhell, mouseclick ... and all of these events worked. Can somebody help with any idea?
AFAIK, scroll event will not work in cell based widgets out of the box if the event's target and the widget are not the same elements.
And GXT's TextArea is a widget that has such DOM structure.
That's all because scroll event is a "non bubbling" event.
AFAIK, GWT widgets, that uses cells, have a special handling for non bubbling events to be dispatched through GWT events system.
And the list of types of supporting non bubbling events are too short and limited to focus, blur, load and error events.
See CellBasedWidgetImplStandard class for details.
First solution, that I may suggest, is to explicitly assign onscroll handler for textarea.
For example:
Event.sinkEvents(textAreaElement, Event.getEventsSunk(textAreaElement) | Event.ONSCROLL);
here textAreaElement - is the textarea DOM element.
But this should be done every time when the cell rerenders its content (e.g. when setValue method called for TextArea widget).
Other solution is a bit hacky and uses private api implementation, but could be applied only once to cell based widget. You can do the same things as done in CellBasedWidgetImplStandard.sinkEvent(Widget, String) e.g. :
WidgetHelper.sinkEvent(logTextArea.getElement(), BrowserEvents.SCROLL);
where WidgetHelper may looks like this:
public class WidgetHelper {
public static void sincEvent(Element element, String typeName){
element.setAttribute("__gwtCellBasedWidgetImplDispatching" + typeName, "true");
sinkEvent0(element, typeName);
}
private static native void sinkEvent0(Element element, String typeName) /*-{
element.addEventListener(typeName,
#com.google.gwt.user.cellview.client.CellBasedWidgetImplStandard::dispatchNonBubblingEvent, true);
}-*/;
}
Probably, this is a subject to create an issue in GWT project.
Related
I am making a Java project with vaadin. Right now I have a user registration form looking like that:
public class RegistrationComponent extends CustomComponent implements View {
public static final String VIEW_NAME = "Registration";
public RegistrationComponent(){
Panel panel = new Panel("Registration Form");
panel.setSizeUndefined();
FormLayout content = new FormLayout();
CheckBox checkBox1, checkBox2, checkBox3;
checkBox1 = new CheckBox("Check Box 1");
checkBox2 = new CheckBox("Check Box 2");
checkBox3 = new CheckBox("Check Box 3");
checkBox1.setRequired(true);
checkBox2.setRequired(true);
TextField mailTextField = new TextField("Email Address");
TextField passwordTextField = new TextField("Password");
TextField confirmPasswordTextField = new TextField("Confirm Password");
final Button submitButton = new Button("Submit");
content.addComponent(mailTextField);
content.addComponent(passwordTextField);
content.addComponent(confirmPasswordTextField);
content.addComponent(checkBox1);
content.addComponent(checkBox2);
content.addComponent(checkBox3);
content.addComponent(submitButton);
content.setSizeUndefined(); // Shrink to fit
content.setMargin(true);
panel.setContent(content);
setCompositionRoot(panel);
//listeners:
submitButton.addClickListener(new Button.ClickListener() {
public void buttonClick(Button.ClickEvent event) {
//
}
});
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event){
//
}
}
Of course, the form doesn't do anything other than being displayed.
What I wanna do, is make Vaadin display error messages next to fields if some requirements are not met. The requirements themselves are not that important (lets say I want email field to contain at least 8 characters). What I wanna know, is: is there any simple built-in way to do that? I was here:
https://vaadin.com/api/com/vaadin/data/Validator.html
but I dont understand how to use a validator, or even if that is what I want to use. I've been looking all over google for usage examples, but so far with no success. Thanks for help!
Vaadin 7
The following applies to Vaadin 7. The validate() method has been removed in Vaadin 8.
All Field types in Vaadin implement the Validatable interface which has the addValidator method that accepts an implementation of Validator as parameter.
So to add a validator that checks the length of the value of a TextField, you would do this:
TextField textField = new TextField();
textField.addValidator(
new StringLengthValidator(
"Must be between 2 and 10 characters in length", 2, 10, false));
Vaadin fields have built-in functionality for displaying the validation errors to the user. By default, the field will be highlighted in red and an exclamation mark will appear next to the field, hovering over this will show a more detailed message to the user.
Automatic Validation
By default, the field will now validate on the next server request which contains a changed value for the field to the server. If the field is set to 'immediate', this will happen when the field looses focus. If the field is not immediate, validation will happen when some other UI action triggers a request back to the server.
Explicit Validation
Sometimes, you may want to exercise more control over when validation happens and when validation errors are displayed to the user. Automatic validation can be disabled by setting validationVisible to false.
textField.setValidationVisible(false);
When you are ready to validate the field (e.g. in a button click listener) you can explicitly call the validate (you can also use commit() if it is a buffered field) method on the TextField instance to trigger validation. validate will throw an InvalidValueException if the value is invalid. If you want to use the builtin display of validation errors included in the TextField component you will also have to set validationVisible back to true.
try {
textField.validate();
} catch (Validator.InvalidValueException ex) {
textField.setValidationVisible(true);
Notification.show("Invalid value!");
}
Note that once validationVisbible is set back to true, validation will happen implicitly so you must remember to set it back to false on the next request if you want to maintain explicit control over validation.
Validation Messages
Individual validation messages can be extracted from the instance of Validator.InvalidValueException which is thrown when validate() or commit() is called.
try {
textField.validate();
} catch (Validator.InvalidValueException ex) {
for (Validator.InvalidValueException cause: ex.getCauses()) {
System.err.println(cause.getMessage());
}
}
Validators
Validators implement the Validator interface and there are several useful validators shipped with Vaadin. Check out the API docs for more information on these: https://vaadin.com/api/7.4.5/com/vaadin/data/Validator.html
Custom validators are easy to implement, here is an example taken from the Book of Vaadin:
class MyValidator implements Validator {
#Override
public void validate(Object value)
throws InvalidValueException {
if (!(value instanceof String &&
((String)value).equals("hello")))
throw new InvalidValueException("You're impolite");
}
}
final TextField field = new TextField("Say hello");
field.addValidator(new MyValidator());
field.setImmediate(true);
layout.addComponent(field);
Problem solved,
Apparently I wasn't looking deep enough before. Here it comes:
field.addValidator(new StringLengthValidator("The name must be 1-10 letters (was {0})",1, 10, true));
all details here:
https://vaadin.com/book/-/page/components.fields.html
Vaadin 8
Using Vaadin 8 com.vaadin.data.Binder easily you can validate your fields. See Binding Data to Forms in the manual.
Create a TextField and a binder to validate the text field.
public class MyPage extends VerticalLayout{
TextField investorCode = new TextField();
Binder<MyBean> beanBinder = new Binder<MyBean>();
//Info : MyBean class contains getter and setter to store values of textField.
public MyPage (){
investorCode.addValueChangeListener(e->valueChange(e));
addComponent(investorCode);
bindToBean();
}
private void bindToBean() {
beanBinder.forField(investorCode)
.asRequired("Field cannot be empty")
.withValidator(investorCode -> investorCode.length() > 0,"Code shold be atleast 1 character long").bind(MyBean::getInvestorCode,MyBean::setInvestorCode);
}
//rest of the code .....
private void valueChange(ValueChangeEvent<String> e) {
beanBinder.validate();
}
}
Call validate() from binder will invoke the validation action.
beanBinder.validate();
to validate the filed. You can call this from anywhere in the page. I used to call this on value change or on a button click.
In a GEF editor, I have the following EditPart:
public class MyLabelEditPart extends AbstractGraphicalEditPart {
#Override
protected IFigure createFigure() {
return new Label();
}
#Override
protected void refreshVisuals() {
MyModel model = (MyModel) getModel();
Label figure = (Label) getFigure();
EditPart parent = getParent();
Font font = new Font(Display.getCurrent(), "sansserif", 11, SWT.BOLD);
figure.setFont(font);
figure.setForegroundColor(ColorConstants.darkGray);
figure.setText(model.getValueString());
parent.refresh();
}
All works fine with most models, but - you will have spotted the error already - I never dispose of the font. So, with a large-ish model of 10k+ tokens, this throws an org.eclipse.swt.SWTError: No more handles. At least I think (hope) this is what causes the error.
Now I cannot figure out how to dispose the font, as the figure for the EditPart is a Draw2D Label, not an SWT Widget. How can I make sure the dreaded error can be circumvented?
Apart from Baz' solution to make the font a static field, a good solution is to use a JFace FontRegistry, as detailed in this strangeoptics blog post.
If you want to keep and reuse all your resources ( images, fonts, colors..etc) at one point rather than using registries, follow this windows builder class
http://code.google.com/p/goclipse/source/browse/trunk/goclipse-n/src/org/eclipse/wb/swt/SWTResourceManager.java?r=445
I am at an SWT application where one can rearrange controls within a shell (or any Composite for that matter) via drag&drop. That's basically no problem, DragSources and DropTargets are all in place and listeners attached accordingly. I even implemented a custom Transfer type for the sake of exercise. Pretty straightforward.
But now the requirement is that a drag should only be initiated, if the ALT key is pressed while the drag gesture is performed, otherwise nothing should be done. (The ALT key is an example, could be CTRL as well.)
So far, I see or have thought about the following approaches. All of them either don't work or are ugly.
\1. Intercept and cancel the DragDetect event
The idea is to cancel the event if the ALT key is not pressed with event.doit = false.
lblPos.addListener(SWT.DragDetect, new Listener() {
public #Override void handleEvent(Event event) {
if ((event.stateMask & SWT.ALT) == 0)
event.doit = false; // XXX: doit will not be evaluated
}
});
However, that doesn't work. The doit flag is apparently not evaluated.
\2. Intercept and cancel the DND.DragStart event.
class RowDragListener implements DragSourceListener {
public #Override void dragStart(DragSourceEvent event) {
if (/* ALT key not pressed */)
event.doit = false;
}
...
}
This has the opposite problem of appraoch 1. While the doit flag is properly evaluated and thus suitable to cancel the drag, there is no stateMask in the event that can be inspected for modifier keys. So the question arises, how can I query the keyboard directly (without installing KeyUp/Down event handlers)? What is the current up/down state of the ALT key?
\3. Combine 1 and 2
Inspect the stateMask in the DragDetect event, store the result somewhere, then react accordingly in the DND.DragStart event. This shouldn't be too hard, but I think it's ugly and should not be done this way. Instead of DragDetect, KeyUp/Down events could be captured and the last known state of the ALT key be stored.
\4. Override Control.dragDetect(Event) or Control.dragDetect(MouseEvent)
These methods ultimately create DragDetect events if they see the conditions for it fulfilled.
Check the event's stateMask and invoke the overridden method from the super class only if the desired modifier key is signalled. Problem here is, from the documentation it is not clear if this is the only code path that is treaded upon a drag gesture. In fact, these two methods are independent from each other (they don't invoke each other), so it's not even clear which one to override. These methods already are two separate ways to initiate a drag gesture. Who knows how many more ways are there? Overriding them all would be error prone, if possible at all, and certainly not clean.
So my questions are:
1. How would you do that? Any other ideas?
2. If approach 2 seems the most reasonable, how is the keyboard queried without resorting to event handlers?
(Sorry for the formatting of this post, i seem to be unable to grasp the syntax. Or maybe it's not my fault, who knows.)
UPDATE: There's one thing to note, which i noticed during the implementation. On Windows, ALT-Drag&Drop has the specific meaning of a link operation (as opposed to move or copy; cmp. DND.DROP_* constants). That's why, if you choose to use the ALT key in a similar fashion, be advised to include the following line at every reasonable occasion in the DropTargetListener.
if (event.detail == DND.DROP_LINK) event.detail = DND.DROP_MOVE;
I have this in the dragEnter, dragOver and dragOperationChanged listener methods and this works quite fine.
You can listen to SWT.DragDetect event, check the state mask and create the drag source only if conditions are met. Then pass the event to the newly created drag source by calling notifyListeneres(). After drag finishes the drag source has to be disposed.
Here is a snippet where drag is initiated only if alt is pressed, and uses text as transfer:
public static void main(String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new GridLayout());
shell.addListener(SWT.DragDetect, new Listener() {
#Override
public void handleEvent(Event event) {
if ((event.stateMask & SWT.ALT) != 0) {
final DragSource dragSource = new DragSource(shell, DND.DROP_MOVE);
dragSource.addDragListener(new DragSourceAdapter(){
#Override
public void dragFinished(DragSourceEvent event) {
dragSource.dispose();
}
#Override
public void dragSetData(DragSourceEvent event) {
event.data = "text";
}
});
dragSource.setTransfer(new Transfer[]{TextTransfer.getInstance()});
dragSource.notifyListeners(SWT.DragDetect, event);
}
}
});
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
I have this code:
DOM.setEventListener(row.getElement(), new ClickListener(){
#Override
public void onClick(Widget sender) {
// TODO Auto-generated method stub
}});
I think the code is fine and ClickListener extends EventListener, but it gives error saying: The method setEventListener(Element, EventListener) in the type DOM is not applicable for the arguments (Element, new ClickListener(){})
The real answer is that you probably don't. While this is available to attach listeners to events, you may only attach a single listener per element - that listen then gets all dom events that have been configured (see DOM.sinkEvents) - and you are responsible for making sure to detach all listeners before the page unloads, else some browsers will leak memory.
Instead, strongly consider using a Widget (and subclasses) to manage events. RootPanel, the base widget that others should be added to, will manage detaching all other widgets from the page to prevent memory leaks.
Additionally, you are able to listen to the events that happen within there based on the kind of event you are after. For example, even on a widget like a Label that doesnt' normally fire mouseover events, you can still attach handlers and get notification:
Label label = new Label();
label.addDomHandler(new MouseOverHandler() {
#Override
public void onMouseOver(MouseOverEvent event) {
// do something
}
}, MouseOverEvent.getType());
RootPanel.get().add(label);
In most cases, you'll be using existing support methods, like Button to get a click event - there are convinience methods already there for you, thanks to interfaces like HasClickHandlers:
Button button = new Button();
button.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
// do something
}
});
panel.add(button);
More on GWT, Widget, and Memory leaks:
https://developers.google.com/web-toolkit/articles/dom_events_memory_leaks_and_you
http://code.google.com/p/google-web-toolkit/wiki/UnderstandingMemoryLeaks
I have an editable JComboBox where I want to take some action whenever the text is changed, either by typing or selection. In this case, the text is a pattern and I want to verify that the pattern is valid and show the matches that result in some test data.
Having done the obvious, attach an ActionHandler, I have found that, for typing, the event seems to fire unreliably, at best (selection is fine). And when it does fire as a result of typing, the text retrieved (using getEditor().getItem(), since getSelectedItem() only gets the text when it was selected from the list) seems to be the text as it was when the last event was fired - that is, it's always missing the character was typed immediately before the action event was fired.
I was expecting the action event to fire after some short delay (500ms to 1 second), but it seems immediately fired upon keying (if it is fired at all).
The only workable alternative I can think of is to simply start a 1 second timer on focus-gained, killing it on focus-lost and doing the work as the timer action if the content is different from last time.
Any thoughts or suggestions?
The code snippets are not particularly interesting:
find.addActionListener(this);
...
public void actionPerformed(ActionEvent evt) {
System.out.println("Find: "+find.getEditor().getItem());
}
The action listener is typically only fired when you hit enter, or move focus away from the editor of the combobox. The correct way to intercept individual changes to the editor is to register a document listener:
final JTextComponent tc = (JTextComponent) combo.getEditor().getEditorComponent();
tc.getDocument().addDocumentListener(this);
The DocumentListener interface has methods that are called whenever the Document backing the editor is modified (insertUpdate, removeUpdate, changeUpdate).
You can also use an anonymous class for finer-grained control of where events are coming from:
final JTextComponent tcA = (JTextComponent) comboA.getEditor().getEditorComponent();
tcA.getDocument().addDocumentListener(new DocumentListener() {
... code that uses comboA ...
});
final JTextComponent tcB = (JTextComponent) comboB.getEditor().getEditorComponent();
tcB.getDocument().addDocumentListener(new DocumentListener() {
... code that uses comboB ...
});
You can use somthing like this:
JComboBox cbListText = new JComboBox();
cbListText.addItem("1");
cbListText.addItem("2");
cbListText.setEditable(true);
final JTextField tfListText = (JTextField) cbListText.getEditor().getEditorComponent();
tfListText.addCaretListener(new CaretListener() {
private String lastText;
#Override
public void caretUpdate(CaretEvent e) {
String text = tfListText.getText();
if (!text.equals(lastText)) {
lastText = text;
// HERE YOU CAN WRITE YOUR CODE
}
}
});
this sounds like the best solution
jComboBox.getEditor().getEditorComponent().addKeyListener(new java.awt.event.KeyAdapter() {
public void keyReleased(java.awt.event.KeyEvent evt) { //add your hadling code here:
} });