Wicket AutoCompleteTextField (both kinds) have bugs when in ModalWindows - java

In Wicket there are 2 versions of an AutoCompleteTextField, one in wicket-extensions and the other in com.googlecode.wicket. I am trying to use one of them in a Wicket ModalWindow, but I am having problems with both.
Both versions are generally OK on a webpage without using modal windows but I need them inside of one. There is some limited examples out there but nothing specific to modal dialogs.
Wicket Extension version:
Using Wicket 6.11.0
final AutoCompleteTextField<T> field = new AutoCompleteTextField<T>( "component", getSelectionModel(), theclass, new AutoCompleteSettings() ) {
private static final long serialVersionUID = 1L;
#Override
protected Iterator<T> getChoices( String input ) {
return AutoCompleteSelect.this.getChoices( input ).iterator();
}
};
field.setOutputMarkupPlaceholderTag( true );
field.add( new AjaxFormComponentUpdatingBehavior( "change" ) {
private static final long serialVersionUID = 1L;
#Override
protected void onUpdate( AjaxRequestTarget target ) {
System.out.println( "Item selected! " + getSelectionModel().getObject() );
// This is never called!
}
} );
add( field );
Originally I was having problems getting this to work at all until I realized that it doesn't fetch the choices until you press the down key. If I put one in a ModalWindow, it's popup location gets screwed up and the suggestions appear off to the bottom right of the screen. Moving the dialog to the lower right of the screen accentuates the problem.
Questions:
How do you get the popup to appear as someone types into the text field, rather than when they hit the down key?
What is the best way to get an ajax onSelected-style call (like the one in the google version?) There is a version in the wicket examples [1] that relies on a form submission but this is no good if you need it before that.
Google Version:
Using com.googlecode.wicket-jquery-ui version 6.11.0
ITextRenderer<T> renderer = new ITextRenderer<T>() {
private static final long serialVersionUID = 1L;
#Override
public String getText( T object ) {
return object == null ? "" : object.toString();
}
#Override
public String getText( T object, String expression ) {
return object == null ? "" : expression + "." + object.toString();
}
};
final AutoCompleteTextField<T> field = new AutoCompleteTextField<T>( "component", getSelectionModel(), renderer, m_class ) {
private static final long serialVersionUID = 1L;
protected void onSelected( AjaxRequestTarget target ) {
System.out.println( "Item " + getSelectionModel().getObject() + " has been selected" );
}
#Override
protected List<T> getChoices( String input ) {
return AutoCompleteSelect.this.getChoices( input );
}
};
Though this initially works better (onSelected is useful and the popup appears in the correct place) putting it in a modal dialog makes the popup appear underneath the dialog. This can be fixed by changing the z-index through CSS (a bit messy but works), but there seems to be an event fired that hides the popup whenever you hover over it meaning nothing can be selected.
Has anyone managed to stop the popup from disappearing?
Is there a better way to deal with the z-index issue?
Wicket Extension Examples: http://www.wicket-library.com/wicket-examples/ajax/autocomplete?0
Google examples: http://www.7thweb.net/wicket-jquery-ui/autocomplete/ConverterAutoCompletePage?0
Thanks in advance.

Update to the latest Wicket 6.19.0 and see if the problems go away. Since 6.11.0 there were a few bugs regarding autocomplete fields and modal windows resolved:
https://issues.apache.org/jira/browse/WICKET-4294
https://issues.apache.org/jira/browse/WICKET-5378
https://issues.apache.org/jira/browse/WICKET-5609
https://issues.apache.org/jira/browse/WICKET-5379
https://issues.apache.org/jira/browse/WICKET-5413
Maybe they will solve your problems without the need of any hacking.

For the Wicket jQuery UI one, this is by design and this should be handled by the user directly
https://groups.google.com/forum/#!searchin/wicket-jquery-ui/autocomplete/wicket-jquery-ui/bwSVY4mlrac/q-dRK1EUr6cJ
<wicket:head>
<style type="text/css">
.ui-autocomplete {
z-index: 9999 !important;
}
</style>
</wicket:head>
If this does not solve your issue, feel free to attach a quickstart that demonstrates the problem in the dedicated forum...
Best regards,
Sebastien

Related

Calculation in Wizard

This is more of a general question. We have a lot of wizard, some of which start a long-running process and display the result after. The question is: what is the correct way to do long calculations?
Formerly most wizards did their calculations in DialogPage#setVisible, something like that:
public void setVisible(final boolean visible) {
if (visible) {
getWizard().getContainer().run(true, true, new MyCalculation());
}
super.setVisible(visible);
}
I don't think that's a good idea, since usually getWizard() gets called a lot in these methods. Moreover, usually the parent wizard gets cast to a specific implementation to get input values from or set the result to other pages. So usually it looks something like this:
public void setVisible(final boolean visible) {
if (visible) {
Input input = ((MyCalculationWizard)getWizard()).getInputPage().getInput();
MyCalculation calculation = new MyCalculation(input);
getWizard().getContainer().run(true, true, calculation);
Output output = calculation.getOutput();
((MyCalculationWizard)getWizard()).getOtherPage().setOutput(output);
}
super.setVisible(visible);
}
Just from looking at the code you know that's very bad style.
So we replaced it with something that calculates in Wizard#getNextPage():
public IWizardPage getNextPage(final IWizardPage page) {
final IWizardPage nextPage = super.getNextPage(page);
if (nextPage == this.myResultPage)
getContainer().run(true, true, new MyCalculation());
return nextPage;
}
That way, the wizard is able to fine-tune a lot better than a page would, and the wizard already knows it's pages and can handle input and output a lot better than a page ever could.
The drawback is: getNextPage() gets called a lot for updating the buttons and every time really the wizard feels like it. So while it works for small processes, it does not cut it for long-running ones.
After some more poking around I found the following to work while overriding Wizard#setContainer:
public void setContainer(final IWizardContainer wizardContainer) {
final IWizardContainer oldContainer = getContainer();
if (oldContainer instanceof WizardDialog)
((WizardDialog) oldContainer).removePageChangingListener(this);
super.setContainer(wizardContainer);
if (wizardContainer instanceof WizardDialog)
((WizardDialog) wizardContainer).addPageChangingListener(this);
}
public void handlePageChanging(final PageChangingEvent event) {
final IWizardPage currentPage = (IWizardPage) event.getCurrentPage();
final IWizardPage nextPage = (IWizardPage) event.getTargetPage();
if (currentPage == this.myInputPage && nextPage == this.myResultPage)
getContainer().run(true, true, new MyCalculation());
}
The big advantage here is that the listener only gets called if the wizard wants to jump between pages, and we are able to really fine-tune the calculation (e.g. to not be called when calling 'Previous'). We are even able to not show the next page after all (event.doit = false).
The drawback is the cast of the container to WizardDialog, because potentially it could be an entirely different implementation.
So the question stands: What is the best way to start long processes in wizards?

Where should I validate JavaFX Property changes?

I have a mvp structured javafx application. There is a view with a textfield, which has its own textProperty of type StringProperty. There is also a model which contains an Object called Item. Item has an IntegerProperty.
Now I'd like to bind these two Properties within my presenter-class, so that they get updated, when one or another changes. Eventhough they have different types, there is the possibility to bind them the following way:
Bindings.bindBidirectional( textField.textProperty(), item.percentProperty(), new NumberStringConverter() );
This works perfectly fine, unless the value of the textfield gets cleared, which results in a NullPointerException, because an empty value of textProperty results in a Null-Value and setting a null Value in IntegerProperty results in a NullPointerException. Can you think of any way to avoid this? Do I have to write my own NumberStringConverter?
Moreover I'd like to define, that Item can only hold a percent value between 0 and 100. The View should be informed, when the value is invalid, so the user can get feedback. Where should I verify these kind of businessrules?
I came up with a first example, but I am not sure, if that should be the way to go, so I'd be curious, if you might have better ideas how to solve this.
class PercentProperty extends SimpleIntegerProperty
{
private InvalidValueListener invalidValueListener = null;
public PercentProperty ( final Integer defaultValue )
{
set( defaultValue );
}
#Override
public void set( final int newValue )
{
if ( isValid( newValue ) )
{
super.set( newValue );
if ( invalidValueListener != null )
invalidValueListener.validValue();
}
else
{
if ( invalidValueListener != null )
invalidValueListener.invalidValue();
}
}
private boolean isValid( final int value )
{
return (value >= 0 && value <= 100);//FIXME: Better use Predicates to define Rules.
}
public void setListener( final InvalidValueListener listener )
{
invalidValueListener = listener;
}
public void removeListener( #SuppressWarnings( "unused" ) final InvalidValueListener listener )
{
invalidValueListener = null;
}
protected void fireInvalidationValue()
{
invalidValueListener.invalidValue();
}
}
interface InvalidValueListener
{
void validValue();
void invalidValue();
}
JavaFX is a simple graphical toolkit, not a comprehensive framework, and this means that lots of things you have to engineer yourself. Data validation is such a thing, and you have to find your own way among your previous experience and others' suggestions.
I would not bind the two properties: the text field should be initialized (just set, not bound, to avoid glitches while the user is typing without her explicit consensus) with the value from the model, and then the integer property should be updated by a listener (a text field's ChangeListener or a listener to the form submission, if appliable and depending on your likes), which is responsible for validating input and reporting errors to the user.
This way you decouple two things that are indeed unrelated: one is a widget for accepting user input (a text you need to parse to get a number), and the other is a number in your model, which is used to make a computation.
As a side note, I would not use two properties altogether, and I'd revisit your three tiers parition. MVP and all MVC derivatives proved to be good patterns to build GUI toolkits, but I was never convinced they were equally good for structuring GUI applications. I mean, if what you call model is a way to share session data between different parts of the application (kind of an events sink) then it's a perfectly legitimate implementation, otherwise I see no use in having a separate bunch of properties grouped in a class. In the latter case, the widgets themselves are the model:
// This is the controller
public class PesonalDetails {
// Model starts here: it's implicitely defined by the widgets
// You may also use #FXML
private final TextField first = new TextField();
private final TextField last = new TextField();
// Model ends here
}
Note I'm not saying MVC should be thrown away and everything should be collapsed in one single file. Just that MVC, MVP, MVVM are design patterns and it's up to you to decide when, where and how to implement them - depending on how much they buy to you. With JavaFX I like to use these tiers:
A visual layout tier (a layout builder implemented in Java or FXML)
Event handling code
If appliable, a data access layer (and you can apply a pattern here, like ActiveRecord)
(The new version of the answer)
I think the best aproach is to not let a user enter an incorrect value in the first place. You can achive this easily with help of JideFX Fields:
FormattedTextField<Integer> field = new FormattedTextField<>();
field.getPatternVerifiers().put("p", new IntegerRangePatternVerifier(0, 100));
field.setPattern("p");
field.valueProperty().bindBidirectional(item.percentProperty());
Particularly FormattedTextField is very convenient because it do text-to-value conversion and validation for you, so there is no need to implement any utility classes yourself.
Links:
JideFX Fields Developer Guide: http://www.jidesoft.com/jidefx/JideFX_Fields_Developer_Guide.pdf
Source code: https://github.com/jidesoft/jidefx-oss
Binary: http://search.maven.org/#search%7Cga%7C1%7Cjidefx

How to force combobox to render autocomplete options?

This is my zul code:
<combobox id="digitalPublisherCombobox" value="#load(ivm.inventory.digitalPublisherName)"
onOK="#command('setDigitalPublisher', digitalPublisherBox = self)"
onSelect="#command('setDigitalPublisher', digitalPublisherBox = self)"
onChanging="#command('setupQuicksearchByEvent', searchlayout = event, prefix = 'PUB', tags = 'PublisherName, PublisherNameTranslit')"
mold="rounded" hflex="1" buttonVisible="false" autodrop="true">
<comboitem self="#{each=entry}" value="#{entry.key}" label="#{entry.value}"/>
</combobox>
And this is QuickSearch implementations:
#Command
public void setupQuicksearchByEvent(#BindingParam("searchlayout")Event event, #BindingParam("prefix") String prefix, #BindingParam("tags") String tags) throws WrongValueException, SearchException, IOException
{
if(event instanceof InputEvent)
{
InputEvent inputEvent = (InputEvent) event;
String inputText = inputEvent.getValue();
List<String> searchFields = Arrays.asList(tags.split(","));
ListModel model = new ListModelMap(ZKLogic.findDocsStartingWith(prefix, searchFields, "proxy", inputText), true);
ListModel subModel = ListModels.toListSubModel(model, Autocompleter.MAP_VALUE_CONTAINS_COMPARATOR, 10);
Combobox searchBox = (Combobox) event.getTarget();
searchBox.setModel(subModel);
searchBox.setItemRenderer(new ComboitemRenderer()
{
#Override
public void render( Comboitem item, Object data, int pos ) throws Exception
{
String publisherString = data.toString();
UID key = getUidFromPublisherString(publisherString);
int startIndex = publisherString.indexOf('=') + 1;
String publisher = publisherString.substring(startIndex);
item.setLabel(publisher);
item.setValue(key);
}
});
}
}
ZKLogic.findDocsStartingWith return map with UID-key and String-value.
With code above I achieved to get dropdown list when I switch to another window. I need to type something, then select another browser or notepad window - and comboitems will be displayed immediately.
So, my question still need answer, is there are any techniques to reproduce this windows switching in code? Or maybe I should do something with autocomplete, because I've got some ac working with preloaded lists, but this thing should return only 10 records from db, instead of all 70000 entries, every time when user type something in the field.
Edit 20/09/2013: Problem still exist. Rename question a bit, because thing that I need is to call render option by force in code. Is there is any way to do it? Code hasn't changed a lot, but print option in render method said, that method can miss two or more onChange events and suddenly render text for one variant.
Maybe you know another autocomplete options in zk framework where database participating? I'm ready to change implementation, if there is a guide with working implementation of it.
Ok I see two problems, you should solve first.
Setting the Renderer in every call of setupQuicksearchByEvent(...).
that is not logical, cos it is the same every time.
Add to the zul combobox tag something like
itemRenderer="#load(ivm.myRenderer)" ....
If you want just 10 items, do not let the db-request return more then 10.
If you use JPA klick here or for sql here or just google a bit.
After you fixed this two issues, we can exclude these as a reason of the unexpected behavior and fix it, if it is still present.
Edit
Ok, I have two possible ways to fix it.
Call Combobox#invalidate()
This schould force zk to rerender the Combobox, but could
lead to low performance and I would not prefer this.
Use Listbox with the select mold instead of Combobox.
To force the rerender, use Listbox#renderAll()
Try setting the selected item on your combobox or throw its related event
Solution is simple. Really. Nothing is better then brute-force, but I think I tried to avoid it and use it in despair.
#Command
public void setupQuicksearchByEvent(#BindingParam("searchlayout")Event event, #BindingParam("prefix") String prefix, #BindingParam("tags") String tags) throws WrongValueException, SearchException, IOException
{
if(event instanceof InputEvent)
{
InputEvent inputEvent = (InputEvent) event;
String inputText = inputEvent.getValue();
List<String> searchFields = Arrays.asList(tags.split(","));
Map<UID, String> publishers = ZKLogic.findDocsStartingWith(prefix, searchFields, "proxy", inputText);
Combobox searchBox = (Combobox) event.getTarget();
searchBox.getChildren().clear();
for (Map.Entry<UID, String > entry : publishers.entrySet())
{
Comboitem item = new Comboitem();
item.setLabel(entry.getValue());
item.setValue(entry.getKey());
searchBox.appendChild(item);
}
}
}

Wicket - DropDownChoice - titles (tooltips) for options

Is there a way to have the DropDownChoice in Wicket assign tooltips (e.g. title attributes) to individual option elements?
I have selectbox items in the following form:
public class SelectBoxItem
{
private Long id;
private String label;
private String description;
}
All items are loaded from the database.
I configure the DropDownChoice component using a ChoiceRenderer to use the ids as keys and the labels as values.
Now I would need to configure it to use the descriptions as tooltip messages as well.
I have only found this related thread on the Internet. Browsing the relevant Wicket classes let me to the same conclusions as the author, e.g. that this was probably not possible with the current versions of the DropDownChoice/ChoiceRenderer classes. Is that right? And in that case, is there a similar component which would allow that?
(For a more comprehensive description of my code base see my other question where I asked about a different problem within the same context.)
Here is my solution for this problem. Many thanks to Andrea Del Bene for the advice.
public class TitledDropDownChoice<T> extends DropDownChoice<T> {
// ... constructors from superclass ...
#Override
protected void appendOptionHtml(AppendingStringBuffer buffer,
T choice, int index, String selected) {
super.appendOptionHtml(buffer, choice, index, selected);
// converts <option value="foo">bar</option> to
// <option value="foo" title="bar">bar</option>
String replString = "value=\"" + getChoiceRenderer()
.getIdValue(choice, index) + "\"";
int pos = buffer.indexOf(replString);
buffer.insert(pos + replString.length(),
" title=\"" + getChoiceRenderer().getDisplayValue(choice) + "\"");
}
}
Have you tried overriding method appendOptionHtml? You could use it to append the desired html (i.e. title="toolTipText").

Wicket serialization/deserialization issue

Assume following component tree:
A
B
C
A has a private field (called filter) and the reference to the filter is passed on to B and C.
In class B a property of the filter is modified through an AjaxLink (so no page refresh).
I see (through) logging that after each ajax call wicket will serialize A, B and C.
Now, everything works fine, clicking in the AjaxLinks in B will nicely update the filter and will refresh C and show the correct information in C (based on the filter).
However, assume if click an AjaxLink in B which will update a property of the filter to (for instance) value 2. Afterwards I press F5, it still works and the page (and all components in it) is deserialized (and again serialized afterwards). I then click on an AjaxLink the changes the value of the mentioned property to 3; this still works fine. If, however, I then do a page refresh (F5) the value of that property suddenly becomes 2 again.
It seems that on the page refresh (when wicket will load the page from disk) wicket is deserializing an older version of the filter.
Schematically:
Page initial load:
=> filter.value = 3 -> serialized
AjaxLink:
=> filter.value = 2 -> serialized
Page refresh:
=> filter.value = 2 -> deserialized/serialized
AjaxLink:
=> filter.value = 3 -> serialized
Page refresh:
=> filter.value = 2 -> deserialized/serialized
It seems that on action 5 the serialized version after action 4 is ignored and the serialized version after action 3 the loaded.
Hoping for a cause, explanation and if possible also the solution :-)
CODE
public class A extends Panel { //creates the filter and passes the reference on
Filter filter = new Filter(TypeEnum.ALL);
public A(final String id) {
super(id);
add(new B("filterPanel", filter));
add(new C("datatable", filter));
}
public class B extends Panel { //updates the filter
Filter filter;;
public B(final String id, Filter filter) {
super(id);
this.filter = filter;
add(new IndicatingAjaxLink<Void>("other") {
#Override
public void onClick(AjaxRequestTarget target) {
filter.setType(TypeEnum.OTHER);
...
}
};
}
}
public class C extends Panel { //uses the filter
Filter filter;;
public C(final String id, Filter filter) {
super(id);
this.filter = filter;
}
public void populateRepeatingView() {
final List<? extends WallEntry> result = service.find(filter);
...
}
}
Code has been simplified and I retained only the (I assume) pertinent stuff.
UPDATE
If I add following in my page class then it works:
#Override
public boolean isVersioned() {
return false;
}
Not sure however about the implications of this and why this makes it work. Isn't the page (de)serialized anymore?
UPDATE
I added following to my Page class:
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
System.err.println("Writing " + this + something to print out the type of the filter);
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
System.err.println("Reading " + this + something to print out the type of the filter);
}
When the Page is loaded first it prints (actually it prints this 5 times, not sure if it's normal):
Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 0, render count = 1]: type = ALL
When I click on AjaxLink 'ALL' (that will update the filter) it still prints:
Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 0, render count = 1]: type = ALL
When I click on AjaxLink 'DISCUSSIONS' (that will update the filter) it still prints:
Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 0, render count = 1]: type = DISCUSSIONS
When I refresh the page (F5) the pageid is updated:
Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 1, render count = 2]: type = DISCUSSIONS
When I click on AjaxLink 'ALL' (that will update the filter) it prints:
Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 1, render count = 1]: type = ALL
So far so good but when I refresh the page now (F5) this is printed out:
Reading [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 0, render count = 1]: type = DISCUSSIONS
Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 2, render count = 2]: type = DISCUSSIONS
The url never changes, it stays http://.../?0
So it deserializes the page with id 0 although the last known page id was 1 and all changes that were done for version 1 are ignored (in this case switching the type from DISCUSSIONS to ALL).
Created a issue in the wicket jira for this: https://issues.apache.org/jira/browse/WICKET-4360
Watch the browser's url When you refresh the page. It probably includes the Wicket page version, so when you refresh a second time, a specific version of the page is deserialized.
Since you keep the filter in your page, it is serialized/deserialized along with its containing components.
A solution would be to put the filter into the session.
As mentioned in the jira issue: https://issues.apache.org/jira/browse/WICKET-4360 you need to setreuseitems on listviews to true or an ajax call will dirty your page when the listview is repopulated:
On F5 Wicket reads (once) the current page. But then it writes a the
page with a new page id because of the usage of ListView
(PropertyListView in DataPanel). By adding
"commentsListView.setReuseItems(true);" all is fine. See WICKET-4286
for a discussion about the ListView problem.
In your log statements, it seems like its only reading on the fifth request. That's probably why it works on the first page refresh.
Wicket serializes the page each time, but it keeps the last version of the page in memory and only de-serializes it if a previous version is requested.
If you check the hashCode of the Filters in the A, B, and C Components, you'll probably see they are different objects after deserialization. Instead of passing around a Filter, pass around a model which returns the Filter from Component A. If component A is a Page, it's really easy because every Component can invoke getPage().getDefaultModel() or getDefaultModelObject(). If Component A is a Panel, try navigating to it with getPage().get("A").getDefaultModel().
I think if the app is using the same Filter object, serialization wont matter.

Categories

Resources