Wicket : Submitting data with a listview inside a form - forms

I have the following issues:
Imagine a wicket list view that looks something like this:
ListView<Question> lvQuestion = new ListView<Question>("lvQuestion",
questions) {
private static final long serialVersionUID = 1L;
#Override
protected void populateItem(ListItem<Question> item) {
Question q = item.getModelObject();
item.add(new Label("question", q.getQuestion()));
List<String> possibleAnswers = getFromDb();
q.setPossibleAnswers(possibleAnswers);
if(q.getComponent().equals("dropDown")){
item.add(new DropDownPanel("questionComponent", q));
}
}
};
This listview is created within a form component; what i need to do is take every question answered using this listview and record them in the database.
Problem being though is that I don't know how many drop down panels there will be so there is always going to be a dynamic set of questions returned.
Is there a way of ensuring that when the parent form is submitted every model in the listview is submitted to the database.

What I do for these situations is maintain a hashmap with the selected option for each question. I update it with AjaxFormComponentUpdatingBehavior such as the following:
First, declare & initialize the map (There may be a more efficient way of doing this)
Map<Question, String> qMap = new HashMap<>();
for(Question q : questions) {
qMap.put(q, "");
}
Then, update the map within your ListView (you probably want to set q as final when you initialize it)
if(q.getComponent().equals("dropDown")) {
DropDownPanel ddp = new DropDownPanel("questionComponent", q);
ddp.add(new AjaxFormComponentUpdatingBehavior("onchange") {
#Override
protected void onUpdate(AjaxRequestTarget art) {
String answer = (String) getFormComponent().getConvertedInput();
qMap.put(q, answer);
}
}
item.add(ddp);
}
This allows you to keep the qMap up to date and process it when the form is submitted.
Alternatively, if you have a String attribute to hold the answer on the Question object and you make sure your ListView.list and your List questions stay consistent, you can do the following in the onUpdate() without needing the map:
q.setAnswer(answer);
questions.set(item.getIndex(), q);
In this case you would just process the questions list in the form's onSubmit().

Related

Unity-How to Create a Dialogue Tree?

So I have this program set up for a simple dialogue tree, where I want to display a question and two options in the unity editor, and if you click one option, you either go to another level of the tree or a leaf. I want to use the composite design pattern to make separate level instances, each with different parameters for one question and two options and add them together into a list. What I'm stuck on is how do I start at the first level and traverse down the tree depending on which button I press. It seems like no matter what I do, it only displays the last level parameters added to the list. The best I can think is maybe add some sort of shifting list function during the button click events. If anyone could shoot some ideas, I would appreciate it. Thank you.
public class Level : MonoBehaviour {
bool button1Pressed;
bool button2Pressed;
private void Start()
{
Level Level1 = new Level("Hello", "Hi", "Shut Up");
Level leaf1 = new Level("Don't be Rude");
Level Level2 = new Level("What you Doing?", "Not Much", "None of your Business");
Level leaf2 = new Level("Well Excuuuuse Me");
Level Level3 = new Level("Can I do that too?", "Sure", "Go Away");
Level leaf3 = new Level("Fine. Be a Jerk");
Level Level4 = new Level("This is boring, can we do something else?", "Why not?", "You're boring");
Level leaf4 = new Level("I'll go be boring somewhere else");
Level Level5 = new Level("You want ice cream?", "Sounds Good", "I'm allergic");
Level leaf5 = new Level("ok.......");
Level leaf = new Level("I Want Chocolate");
Level1.add(Level1);
Level1.add(leaf1);
Level2.add(Level3);
Level2.add(leaf2);
Level3.add(Level4);
Level3.add(leaf3);
Level4.add(Level5);
Level4.add(leaf4);
Level5.add(leaf5);
Level5.add(leaf);
}
public static Text Textbox;
public static Button Button1;
public static Button Button2;
public string OptionA;
public string OptionB;
public string Question;
public string Leaf;
private List<Level> levels;
public Level(string question, string optionA, string optionB)
{
this.Question = question;
this.OptionA = optionA;
this.OptionB = optionB;
GameObject.FindGameObjectWithTag("Level").GetComponentInChildren<Text>().text = Question;
GameObject.FindGameObjectWithTag("OptionA").GetComponentInChildren<Text>().text = OptionA;
GameObject.FindGameObjectWithTag("OptionB").GetComponentInChildren<Text>().text = OptionB;
levels = new List<Level>();
}
public Level(string leaf)
{
this.Leaf = leaf;
Textbox.text = leaf;
}
public void add(Level lvl)
{
levels.Add(lvl);
}
public List<Level> getLevels()
{
return levels;
}
public void Button1Pressed()
{
}
public void Button2Pressed()
{
}
}
public class Initializer : MonoBehaviour {
public Text Textbox;
public Button Button1;
public Button Button2;
void Awake()
{
Level.Textbox = this.Textbox;
Level.Button1 = this.Button1;
Level.Button2 = this.Button2;
}
}
The short answer: All nodes know their parent and children.
There are a few ways to approach this problem. I'll explain an approach using a tree structure with multiple node classes. First we can examine the interactions with the player as you've outlined:
Making a dialog choice (speaking)
Observing a dialog response (listening)
There's also some important conditions we need to consider:
User terminated conversations
AI terminated conversations
From this outline we can build our tree with some classes. I've scratched out some examples but I haven't tested them. Hopefully it conveys the idea and you can build your own solution. It may also be more useful for you to make a single Node class that just knows which type it is. Another improvement would be using an interface or some way to generalize the parent/child relationship which would allow for more complicated tree structures.
class ChoiceNode
{
public ChoiceNode(ResponseNode myParent)
{
parent = myParent;
}
ResponseNode parent = null;
List<ResponseNode> children = new List<ResponseNode>;
bool canSayGoodbye = true;
}
class ResponseNode
{
public ResponseNode(ChoiceNode myParent, string myMessage)
{
parent = myParent;
parent.children.Add(this);
response = myMessage;
}
ChoiceNode parent;
ChoiceNode child;
string response;
}
We should now be able to use a method to display dialog choice by simply enumerating the ResponseNode.children. Then whenever we make a dialog choice we want to display the ResponseNode.response and then move to the ResponseNode.child to find the next set of dialog choices. When parent == null we're at the root branch. When child == null we display some termination text.
I hope this is helpful and gives you some ideas.
Ok, I tried to understand the logic of your code but there are several things that I don't understand, maybe it's better if I try to explain my "Unity dialogue tree".
First:
We need to create a Tree object. If you just want a binary tree you just need tree variables:
public class BinaryTree{
private string root;
private BinaryTree left;
private BinaryTree right
getters/setters
}
this object don't even need to be a Unity component.
root is "dialog"
left is "optionA"
right is "optionB"
if you don't need multiple answer just make left=right.
If you need a way to identify multiple answer I suggest you to create an object like this:
public class Tree{
private Dictionary<string,string> root;
private List<Tree> next;
getters/setters
}
root is again your dialog. A key (that identify your answer) and a value that is the actual dialog.
next is a List of Tree (you identify your answer by making a loop in your next and by checking the key of the dictionary).
Now in the Start method you need to create a new Tree object and set your nexts.
Example
Start(){
BinaryTree bn = new BinaryTree();
bn.Root = "Is this a question?";
BinaryTree left = new BinaryTree();
left.Root = "Nope";
bn.Left = left;
BinaryTree right = new BinaryTree();
right.Root = "Yes it is...";
bn.Right = right;
}
Almost the same for the Tree version:
Start(){
Tree bn = new Tree();
bn.Root = new Dictionary<string, string>();
bn.Root.Add("key1", "Do you need something?");
bn.Next = new List<Tree>();
Tree answer1 = new Tree();
answer1.Root = new Dictionary<string, string>();
answer1.Root.Add("key2", "Yes");
bn.Next.Add();
... iterate...
}
Of course this is just a basic example. The best way to initialize that is to add your dialog to an array and iterate.
Anyway.
Now you can (for example) create a button. In the Start you can Initialize it's text value to your root. In the PointerDown/Clicked method you can make an array of the possible answer keys and, for example, you can decide to generate multiple buttons for multiple answers (or just use 2 static button answer with the BinaryTree) and change the text based on the answer root value (or left/right value). each of the answer button should, in the PointerDown/Clicked method, send the key value (or left/right object) of your user selection (in practice the next value that will display in the main question button).
Of course the "question button" once clicked again should display the next of your answer (and maybe you can decide to add a bool question variable to your object... or maybe you can just decide to use only the left side for a question... or maybe you can decide that if there is only 1 value in the next List that value is a question and it should just show it in the main button text value).
And if the next is null of course you can end the conversation.
There are multiple ways to do this.

Wicket not updating page after form submit

I have this code in my page constructor:
private String selectedAwsId;
private String selectedIsReal;
//these two are actually outside the constructor, and getters and setters for these two strings not shown
List<AwsCredentials> awsCredentials = (List<AwsCredentials>)getAwsCredentials();
List<String> awsIds = new ArrayList<String>();
for (AwsCredentials cred : awsCredentials){
awsIds.add(cred.getAwsId());
}
selectedAwsId = awsIds.get(0);
List<String> yesOrNo = Arrays.asList(new String[] { "sandbox", "real"});
selectedIsReal = "sandbox";
Form selectAwsCredentialsForm = new Form("selectAwsCredentialsForm"){
#Override
public void onSubmit() {
super.onSubmit();
//TODO: why isn't this updating the form?
}
};
add(selectAwsCredentialsForm);
selectAwsCredentialsForm.add(new DropDownChoice("selectAwsCredentialsDropdown", new PropertyModel(this, "selectedAwsId"), awsIds));
selectAwsCredentialsForm.add(new DropDownChoice("selectRealOrSandboxHitsDropdown", new PropertyModel(this, "selectedIsReal"), yesOrNo));
The first time I render the page, this works fine. But when I change the selection in either of the DropDownChoices and submit the form, the page doesn't change (values in the selectedAwsId and selectedIsReal aren't altered accordingly). Is there something I'm missing in my understanding of how forms work? Does the entire page get refreshed when a form gets submitted (does the constructor get run again?)
You might want to construct a model and set this as your form's model. (This is what I do.) Submitting the form (and if all is coded appropriately) will then result in the form's model being updated.

Form fields are reset on validation error

I have a rather complex form in the way that the number of form fields is flexibel. In short, the model object is a TLabel (TranslationLabel) that contains a Map of values (translations). Language here is an enum so the idea is that the number of fields (text areas) for which a translation is given depends on the values in this enum.
This is my form (simplified):
public class TranslationEditForm extends Form {
private final static List<Language> LANGUAGES = newArrayList(Language.values());
public TranslationEditForm(String id, final TranslationLabelView label) {
super(id, new CompoundPropertyModel<TranslationLabelView>(label));
ListView<Language> textAreas = new ListView<Language>("translationRepeater", LANGUAGES) {
#Override
protected void populateItem(final ListItem<Language> itemLang) {
//loop through the languages and create 1 textarea per language
itemLang.add(new Label("language", itemLang.getModelObject().toString()));
Model<String> textModel = new Model<String>() {
#Override
public String getObject() {
//return the value for current language
return label.getValue(itemLang.getModelObject());
}
#Override
public void setObject(String object) {
//set the value for current language
label.getTranslations().put(itemLang.getModelObject(), object);
}
};
itemLang.add(new TextArea<String>("value", textModel).setRequired(true));
}
};
//add the repeater containing a textarea per language to the form
this.add(textAreas);
}
}
Now, it works fine, 1 text area is created per language and its value is also set nicely; even more when changed the model gets updated as intended.
If you submit the form after emptying a text area (so originally there was a value) then of course there is a validation error (required). Normal (wicket) behaviour would be that the invalid field is still empty but for some reason the original value is reset and I don't understand why.
If I override onError like this:
#Override
protected void onError() {
this.updateFormComponentModels();
}
then it is fine, the value of the field is set to the submitted value (empty) instead of the original value.
Any idea what is causing this? What is wicket failing to do because the way I've set up the form (because with a simple form/model this is working fine as are the wicket examples)?
Posted as answer, so the question can be marked as solved:
ListView does recreate all its items at render time. This means that the validation will be broken. Have a look at API doc of the ListView
Calling setReuseItems() on the ListView solves this.
Regards,
Bert

GWT RequestFactory + CellTable

Does anyone know for an example of GWT's CellTable using RequestFactory and that table is being edited? I would like to list objects in a table (each row is one object and each column is one property), be able to easily add new objects and edit. I know for Google's DynaTableRf example, but that one doesn't edit.
I searched Google and stackoverflow but wasn't able to find one. I got a bit confused with RF's context and than people also mentioned some "driver".
To demonstrate where I currently arrived, I attach code for one column:
// Create name column.
Column<PersonProxy, String> nameColumn = new Column<PersonProxy, String>(
new EditTextCell()) {
#Override
public String getValue(PersonProxy person) {
String ret = person.getName();
return ret != null ? ret : "";
}
};
nameColumn.setFieldUpdater(new FieldUpdater<PersonProxy, String>() {
#Override
public void update(int index, PersonProxy object, String value) {
PersonRequest req = FaceOrgFactory.getInstance().requestFactory().personRequest();
PersonProxy eObject = req.edit(object);
eObject.setName(value);
req.persist().using(eObject).fire();
}
});
and my code for data provider:
AsyncDataProvider<PersonProxy> personDataProvider = new AsyncDataProvider<PersonProxy>() {
#Override
protected void onRangeChanged(HasData<PersonProxy> display) {
final Range range = display.getVisibleRange();
fetch(range.getStart());
}
};
personDataProvider.addDataDisplay(personTable);
...
private void fetch(final int start) {
lastFetch = start;
requestFactory.personRequest().getPeople(start, numRows).fire(new Receiver<List<PersonProxy>>() {
#Override
public void onSuccess(List<PersonProxy> response) {
if (lastFetch != start){
return;
}
int responses = response.size();
if (start >= (personTable.getRowCount()-numRows)){
PersonProxy newP = requestFactory.personRequest().create(PersonProxy.class);
response.add(newP);
responses++;
}
personTable.setRowData(start, response);
personPager.setPageStart(start);
}
});
requestFactory.personRequest().countPersons().fire(new Receiver<Integer>() {
#Override
public void onSuccess(Integer response) {
personTable.setRowCount(response+1, true);
}
});
}
I try to insert last object a new empty object. And when user would fill it, I'd insert new one after it. But the code is not working. I says that user is "attempting" to edit a object previously edited by another RequestContext.
Dilemmas:
* am I creating too many context'es?
* how to properly insert new object into celltable, created on the client side?
* on fieldUpdater when I get an editable object - should I insert it back to table or forget about it?
Thanks for any help.
am I creating too many context'es?
Yes.
You should have one context per HTTP request (per fire()), and a context that is not fire()d is useless (only do that if you/the user change your/his mind and don't want to, e.g., save your/his changes).
You actually have only one context to remove here (see below).
Note that your approach of saving on each field change can lead to "race conditions", because a proxy can be edit()ed by at most one context at a time, and it remains attached to a context until the server responds (and once a context is fired, the proxy is frozen –read-only– also until the server responds).
(this is not true in all cases: when onConstraintViolation is called, the context and its proxies are unfrozen so you can "fix" the constraint violations and fire the context again; this should be safe because validation is done on the server-side before any service method is called).
how to properly insert new object into celltable, created on the client side?
Your code looks OK, except that you should create your proxy in the same context as the one you'll use to persist it.
on fieldUpdater when I get an editable object - should I insert it back to table or forget about it?
I'm not 100% certain but I think you should refresh the table (something like setRowData(index, Collections.singletonList(object)))
BTW, the driver people mention is probably the RequestFactoryEditorDriver from the Editor framework. It won't help you here (quite the contrary actually).

How can I use RequestFactory to create an object and initialize a collection whithin it with objects retrieved from another ReqFactory?

I am struggling with an issue using RequestFactory in GWT.
I have a User object : this object has login and password fields and other fields which are of collection type.
public class User {
private String login;
private String password;
private Set<Ressource> ressources;
// Getters and setters removed for brievety
}
I need to persist this object in db so I used RequestFactory because it seems like a CRUD-type operation to me.
Now for the RequestFactory part of the code, this is how I have tried to do it :
I create a UserRequestContext object to create a request object for the new User. Which gives something like :
public interface MyRequestFactory extends RequestFactory {
UserRequestContext userRequestContext();
RessourceRequestContext ressourceRequestContext();
}
and to create the user object I have something like this :
public class UserAddScreen extends Composite {
private UserProxy userProxy;
EventBus eventBus = new SimpleEventBus();
MyRequestFactory requestFactory = GWT.create(MyRequestFactory.class);
...
public UserAddScreen() {
...
requestFactory.initialize(eventBus);
}
public showUserAddScreen() {
// Textbox for password and login
// Listbox for ressources
}
}
I have tried to implement it as a wizard. So at the beginning of the UserAddScreen, I have a
a userProxy object.
This object fields are initialized at each step of the wizard :
the first step is adding the login and password
the second step is adding ressources to the userProxy object.
for this last step, I have two list boxes the first one containing the list of all the ressources i have in my DB table Ressources that I got from RessourceRequestContext.getAllRessource (I have a loop to display them as listbox item with the RessourceId as the value) and the second allows me to add the selected Ressources from this first listbox. Here is the first listbox :
final ListBox userRessourcesListBox = new ListBox(true);
Receiver<List<RessourceProxy>> receiver = new Receiver<List<RessourceProxy>>() {
#Override
public void onSuccess(List<RessourceProxy> response) {
for(RessourceProxy ressourceProxy : response) {
ressourcesListBox.addItem(ressourceProxy.getNom() + " " + ressourceProxy.getPrenom(), String.valueOf(ressourceProxy.getId()));
}
}
};
RessourceRequestContext request = requestFactory.ressourceRequestContext();
request.getAllRessource().fire(receiver);
So, as you can see, my code loops over the retrieved proxies from DB and initializes the items within the listbox.
Here are the control buttons :
final Button addButton = new Button(">");
addButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
for (int i = 0; i < ressourcesListBox.getItemCount(); i++) {
boolean foundInUserRessources = false;
if (ressourcesListBox.isItemSelected(i)) {
for (int j = 0; j < userRessourcesListBox
.getItemCount(); j++) {
if (ressourcesListBox.getValue(i).equals(
userRessourcesListBox.getValue(j)))
foundInUserRessources = true;
}
if (foundInUserRessources == false)
userRessourcesListBox.addItem(ressourcesListBox
.getItemText(i), ressourcesListBox
.getValue(i));
}
}
}
});
So when somebody selects one or more users and click on a ">" button, all the selected items go to the second listbox which is named userRessourceListBox
userRessourcesListBox.setWidth("350px");
userRessourcesListBox.setHeight("180px");
After that, I have a FINISH button, which loops over the items in the second listbox (which are the ones I have selected from the first one) and I try to make a request (again) with RequestFactory to retrieve the ressourceProxy object and initialize the userProxy ressources collection with the result
final Button nextButton = new Button("Finish");
nextButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
RessourceRequestContext request = requestFactory.ressourceRequestContext();
for(int i = 0; i < userRessourcesListBox.getItemCount(); i++) {
Receiver<RessourceProxy> receiver = new Receiver<RessourceProxy>() {
#Override
public void onSuccess(RessourceProxy response) {
userProxy.getRessource().add(response);
}
};
request.find(Long.parseLong(userRessourcesListBox.getValue(i))).fire(receiver);
}
creationRequest.save(newUserProxy).fire(new Receiver<Void>() {
#Override
public void onSuccess(Void response) {
Window.alert("Saved");
}
});
}
});
Finally, (in the code above) I try to save the UserProxy object (with the initial request context I have created userProxy with)... but it doesn't work
creationRequest.save(newUserProxy).fire(...)
It seems like when looping over the result in the onSuccess method :
userProxy.getRessource().add(response);
I retrieve the response (of type RessourceProxy) but beyond this method, for example when I try to save the userProxy object AFTER THE LOOP, there are no RessourceProxy objects in the ressourceProxy collection of userProxy...
Have you guys ever experienced something like this ?
Perhaps I am not doing it right : do I have to get the ressource with the UserRequestContext ? so that my newUser object and ressources are managed by the same request Context ?
if yes then I think it's a little bit weird to have something mixed together : I mean what is the benefit of having a Ressource-related operation in the User-related request context.
any help would be really really ... and I mean really appreciated ;-)
Thanks a lot
The message "… has been frozen" means that the object has been either edit()ed or passed as an argument to a service method, in another RequestContext instance (it doesn't matter whether it's of the same sub-type –i.e. UserRequestContext vs. RessourceRequestContext– or not) which hasn't yet been fire()d and/or the response has not yet come back from the server (or it came back with violations: when the receiver's onViolation is called, the objects are still editable, contrary to onSuccess and onFailure).
UPDATE: you have a race condition: you loop over the resource IDs and spawn as many requests as the number of items selected by the user, and then, without waiting for their response (remember: it's all asynchronous), you save the user proxy. As soon as you fire() that last request, the user proxy is no longer mutable (i.e. frozen).
IMO, you'd better keep the RessourceProxys retrieved initially and use them directly in the user proxy before saving it (i.e. no more find() request in the "finish" phase). Put them in a map by ID and get them from the map instead of finding them back from the server again.