Dynamically add and remove components without AJAX? - java

I have a dynamic form that I'm writing in Wicket with a handful of forms that get duplicated with a "Click here to add more issues" type of button. I've written a very basic (I'm still new to Wicket) AJAXy listener that mostly works, but I can't figure out how to remove or even hide the items in my ListView.
This makes me wonder, is there any way to just have some JS duplicate the form fields? Before I retooled the form with Wicket components I had the Jquery Dynamic Form Plugin duplicating the values. This worked great and was very easy to understand (an important plus). However I can't figure out how this would affect Wicket if I simply used the plugin instead of something like
//Issue box magic
final MarkupContainer rowPanel = new WebMarkupContainer("issuesPanel");
rowPanel.setOutputMarkupId(true);
form.add(rowPanel);
ArrayList numIssues = new ArrayList();
numIssues.add(new Object());
numIssues.add(new Object());
numIssues.add(new Object());
final ListView lv = new ListView("issuesBox", numIssues) {
#Override
protected void populateItem(ListItem item) {
int index = item.getIndex() + 1;
item.add(new DropDownChoice("issues", combinedIssues));
item.add(new TextField<String>("note"));
}
};
lv.setReuseItems(true);
rowPanel.add(lv);
form.add(new AjaxSubmitLink("addIssue", form) {
#Override
public void onSubmit(AjaxRequestTarget target, Form form) {
lv.getModelObject().add(new Object());
if (target != null)
target.add(rowPanel);
}
}.setDefaultFormProcessing(false));
form.add(new AjaxSubmitLink("removeIssue", form) {
#Override
public void onSubmit(AjaxRequestTarget target, Form form) {
//Wicket gets very angry when you remove components, so just hide it (recommended way)
if (target != null) {
Component lastObject = (Component)lv.get(lv.getList().size() - 1);
lastObject.setVisible(false);
log.debug("Components " + lv.get );
}
}
}.setDefaultFormProcessing(false));
The JQuery plugin is much easier to use, but how can I tell Wicket that there are more fields than it thought there were?
Note I'm also trying to avoid the AJAX call just to add a new issue which in the siltation I'm using the app in might become a problem.
Any suggestions?

You can't just add form elements in html via ajax. With Wicket, you have a compenent tree in html and Java, and both must match. In the end, Wicket must know what todo with the values when you submit the form.
Not sure I understand your particular requirements, but I guess a similar problem might be that of adding adresses to an person. Here, I would build a AdressEditPanel that contains a form with all the fields that are required for one adress. That Panel would have an PersonAdress (as an example) Object as Model.
On the Person, you would have an Collection of Adresses and use a ListView to render and edit each Adress. In the populateItem() function of the ListView you would add one instance of a AdressEditPanel to the item.
If you need to add an Adress to an person, simply add an entry to the collection of adresses and redisplay the form (I would work with out Ajax first, and once it works do the minimal change required to switch to ajax).
Hope that helps

If you invoke removeAll() on ListView after you have modified its model object you should solve your problem. For example
new AjaxSubmitLink("addIssue", form) {
#Override
public void onSubmit(AjaxRequestTarget target, Form form) {
lv.getModelObject().add(new Object());
lv.removeAll();
if (target != null)
target.add(rowPanel);
}
However I don't know if this could cause problems to form validation, but I think it should be safe.
UPDATE
If you want to apply the same technique for removing link you should write something like this:
new AjaxSubmitLink("removeIssue", form) {
#Override
public void onSubmit(AjaxRequestTarget target, Form form) {
if (target != null) {
lv.getModelObject().remove(lv.getList().size() - 1);
lv.removeAll();
target.add(rowPanel);
}
}
}
However without AJAX this is not possible. The only way is to use JS and on the server side read the request parameters coming from form.

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?

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);
}
}
}

Obtaining all name-value pairs in a form using Jsoup

I want to automate posting of a number of HTML forms using Jsoup and HttpClient. Most of those forms have hidden fields (with session ids, etc.) or have default values that I'd rather leave alone.
Coding each of the form submissions individually -- extracting each of said hidden or default values from the page -- is extremely tedious, so I thought about writing a generic method to obtain the list of HTTP parameters for a given form.
It is not a trivial piece of code, though, because of the variety of input tags and field types, each of which may need specific handling (e.g. textareas, checkboxes, radio buttons, selects, ...) so I thought I'd first search/ask in case it already exists.
Note: Jsoup and HttpClient are a given; I can't change that -- so please no need to provide answers suggesting other solutions: I have a Jsoup Document object and I need to build an HttpClient HttpRequest.
So I've ended up writing it. I would still prefer to swap for something field-tested (and hopefully maintained elsewhere), but in case it helps anyone landing here...
Not thoroughly tested and without support for multipar/form-data, but works in the few examples I've tried:
public void submit(String formSelector, List<String> params) {
if (params.size() % 2 != 0) {
throw new Exception("There must be an even number of params.");
}
Element form= $(formSelector).first();
Set<String> newParams= Sets.newHashSet();
for (int i=0; i < params.size(); i+= 2) {
newParams.add(params.get(i));
}
List<String> allParams= Lists.newArrayList(params);
for (Element field: form.select("input, select, textarea")) {
String name= field.attr("name");
if (name == null || newParams.contains(name)) continue;
String type= field.attr("type").toLowerCase();
if ("checkbox".equals(type) || "radio".equals(type)) {
if (field.attr("checked") != null) {
allParams.add(field.attr("name"));
allParams.add(field.attr("value"));
}
}
else if (! fieldTypesToIgnore.contains(type)) {
allParams.add(field.attr("name"));
allParams.add(field.val());
}
}
String action= form.attr("abs:action");
String method= form.attr("method").toLowerCase();
// String encType= form.attr("enctype"); -- TODO
if ("post".equals(method)) {
post(action, allParams);
}
else {
get(action, allParams);
}
}
($, get, and post are methods I already had lying around... you can easily guess what they do).
Jsoup has a formData method in the FormElement class; it works in simple cases, but it doesn't always do what I need, so I ended up writing some custom code too.

How can I (un)hide a SWT TableItem?

I am trying to allow my user to search through a table of information, dynamically hiding/showing results that contain the search. I have the hiding part down, and it works well, but I'm having trouble showing the table item again once the search criteria is changed.
Here is my hide code:
searchField.addModifyListener(new ModifyListener() {
#Override
public void modifyText(ModifyEvent arg0) {
modified = true;
for (int i = 0; i < table.getItems().length; i++) {
if (!(table.getItem(i).getText(2)
.contains(searchField.getText()))) {
table.getItem(i).dispose();
}
}
if ("".equals(searchField.getText())) {
modified = false;
//where I would want to un-hide items
}
}
});
Looking at your code, it seems you try to hide the item by calling dispose(). If you dispose a widget, it is gone for good. You cannot get it back.
If you want to unhide it again, will have to create a new item at the position of the previously hidden one with the same content.
Isn't it better to actually operate with some kind of a table model and JFace bindings, rather, then do it like that? And yes, disposing is not hiding. You should probably remove the item from the table.
You have probably to save the data from TableItem into collection before you call dispose. Then when you search again you could check that collection and if matches are found, then insert back into Table by creating new TableItem.

CheckboxCellEditor shows text and not a check box

I'm using the following
org.eclipse.jface.viewers.CheckboxCellEditor.CheckboxCellEditor(Composite parent)
I'm creating a table viewer with cellEditors and doing the following
CellEditor[] editors = new CellEditor[columnNames.length];
editors[7] = new CheckboxCellEditor(table);
I have a CellModifier that has the following
public Object getValue(Object element, String property) {
Object result = null;
...
result = Boolean.valueOf(task.isDfRequested());
return result;
}
public void modify(Object element, String property, Object value) {
item.isSelected(((Boolean)value).booleanValue());
}
Finally I have a LabelProvider that has the following
public String getColumnText(Object element, int columnIndex) {
String result = "";
try {
result = Boolean.toString(item.isSelected());
} catch (Exception ex) { }
break;
However, in my UI instead of having a check box I have the word true or false && clicking it results in switching state to false or true. Any ideas on why I don't have a checkbox??
I've searched in the source code of CheckboxCellEditor class and in the constructor the control associated to the CellEditor is created in the createControl(Composite parent) method. This method is abstract in CellEditor class and it's implemented like this in CheckboxCellEditor:
protected Control createControl(Composite parent) {
return null;
}
So a control is not created, that's why you don't see the checkbox. In the documentation of the Class you can read:
Note that this implementation simply
fakes it and does does not create any
new controls. The mere activation of
this editor means that the value of
the check box is being toggled by the
end users; the listener method
applyEditorValue is immediately called
to signal the change.
I solved this using a ComboBoxCellEditor with yes and no items.
Regards.
Well, I have no idea how SWT works or what component you are even talking about.
But I do know that when using Swing you can have custom editors for a column in a JTable. If you don't tell the table the class of data for the column then the toString() method of the data is invoked. But if you tell the table that Boolean data is displayed in the column then the table will use the check box editor.
Sounds like a similiar symptom, but I don't know your particular solution.
What I've decided to do is to just implement a dirty hack others have been using.
Create two images of check boxes, one checked the other not checked. Switch the state between the two based on the boolean.
It's not perfect, but for now it gets the job done

Categories

Resources