I don't think this is the TAP5-733 bug. Running the code I've recreated the exception and yes, Blackbird reported it as a communication error, but the log shows Its root cause was an NPE caused by _personHolders being null.
Caused by: java.lang.NullPointerException at jumpstart.web.pages.examples.tables.AjaxFormLoop1New.onAddRow(AjaxFormLoop1New.java:106) at jumpstart.web.pages.examples.tables.AjaxFormLoop1New.dispatchComponentEvent(AjaxFormLoop1New.java) at org.apache.tapestry5.internal.structure.ComponentPageElementImpl.dispatchEvent(ComponentPageElementImpl.java:902) at org.apache.tapestry5.internal.structure.ComponentPageElementImpl.triggerContextEvent(ComponentPageElementImpl.java:1081) ... 68 more I think it's due to the conversation id never appearing in the page context. Try this change to onActivate() - when you start a conversation redirect to the same page so that onPassivate() gets called and therefore the conversation id goes into the URL. Object onActivate(EventContext context) throws Exception { if (context.getCount() == 0) { setupPersonHolders(); _conversationId = startConversation(_personHolders); return this; } else { _conversationId = context.get(String.class, 0); _personHolders = getPersonHoldersFromConversation(_conversationId); if (_personHolders == null) { setupPersonHolders(); _conversationId = startConversation(_personHolders); return this; } } return null; } Also, don't get in the habit of initialising fields in the declaration, eg. > private String _conversationId = null; The reason is that you'll rarely get a freshly instantiated page - more likely is you'll get one that Tapestry has pulled out of its pool of page instances. Cheers, Geoff On 14/03/2010, at 2:11 PM, Greg Pagendam-Turner wrote: > Guys, > > I'm trying to work out how to fix the AjaxFormLoop example in Jump Start. > > http://jumpstart.doublenegative.com.au:8080/jumpstart/examples/tables/ajaxformloop1 > > The code recommends that it should use conversations instead of session > persistence. I've changed the code to use conversations but still get > the hidden from field element error when clicking the "Add Row" link as > mentioned in the JIRA https://issues.apache.org/jira/browse/TAP5-733. > > This is because there is nothing in the rendered partial xml that > contains a tag that the hidden field can be placed after (such as input, > select, textarea, label, p, div, td or li). > > Stepping through the code shows that the markup retruned when clicking > Add Row is just: > <ajax-partial></ajax-partial> > > I'd appreciate any clues on where to look next please. > > Regards, > > Greg. > > PS: Modified AjaxFormLoop1.java is > > package jumpstart.web.pages.examples.tables; > > import java.text.DateFormat; > import java.text.Format; > import java.util.ArrayList; > import java.util.List; > import java.util.Locale; > > import jumpstart.business.domain.examples.Person; > import jumpstart.business.domain.examples.iface.IPersonServiceLocal; > import jumpstart.client.IBusinessServicesLocator; > import jumpstart.web.commons.Conversation; > import jumpstart.web.commons.Conversations; > import jumpstart.web.commons.ExceptionUtil; > import jumpstart.web.pages.Index; > import jumpstart.web.pages.examples.wizard.WizardUsingComponents.Step; > import jumpstart.web.state.examples.wizard.CreditRequest; > > import org.apache.tapestry5.BindingConstants; > import org.apache.tapestry5.ComponentResources; > import org.apache.tapestry5.EventContext; > import org.apache.tapestry5.ValueEncoder; > import org.apache.tapestry5.annotations.Component; > import org.apache.tapestry5.annotations.InjectPage; > import org.apache.tapestry5.annotations.Parameter; > import org.apache.tapestry5.annotations.Persist; > import org.apache.tapestry5.annotations.Property; > import org.apache.tapestry5.annotations.SessionState; > import org.apache.tapestry5.corelib.components.Form; > import org.apache.tapestry5.ioc.annotations.Inject; > > public class AjaxFormLoop1 { > private String _conversationId = null; > > @SessionState > private Conversations _conversations; > > // We've used "session" persistence but it is risky. Better > techniques include wrapping this field in a > // Conversation (see the Wizard examples) or persisting it in the > database. > @Parameter(defaultPrefix = BindingConstants.PROP) > @Property > //@Persist > private List<PersonHolder> _personHolders; > > @SuppressWarnings("unused") > @Property > private PersonHolder _personHolder; > > private List<Person> _persons; > > @Component(id = "personsedit") > private Form _form; > > @Inject > private IBusinessServicesLocator _businessServicesLocator; > > @InjectPage > private AjaxFormLoop2 _page2; > > @Inject > private Locale _currentLocale; > > @Inject > private ComponentResources _resources; > > Object[] onPassivate() { > return new Object[] { _conversationId }; > } > > Object onActivate(EventContext context) throws Exception { > if (context.getCount() == 0) { > setupPersonHolders(); > _conversationId = startConversation(_personHolders); > } > else > { > _conversationId = context.get(String.class, 0); > } > > _personHolders = getPersonHoldersFromConversation(_conversationId); > > return null; > } > > void setupPersonHolders() > { > // Get all persons - ask business service to find them (from > the database) > _persons = getPersonService().findPersons(); > > _personHolders = new ArrayList<PersonHolder>(); > for (Person person : _persons) { > _personHolders.add(new PersonHolder(person, false, > person.getId())); > } > } > > // Form triggers the PREPARE event during form render and form > submission. > > void onPrepare() { > if (!_form.getHasErrors()) { > if (_personHolders == null) { > //setupPersonHolders(); > _personHolders = > getPersonHoldersFromConversation(_conversationId); > } > } > } > > PersonHolder onAddRow() { > // Create a skeleton Person and add it to the displayed list > with a unique key > Person newPerson = new Person(); > PersonHolder newPersonHolder = new PersonHolder(newPerson, > true, 0 - System.nanoTime()); > _personHolders.add(newPersonHolder); > > return newPersonHolder; > } > > void onRemoveRow(PersonHolder personPlus) { > int index = _personHolders.indexOf(personPlus); > PersonHolder holder = _personHolders.get(index); > > // If the person is new, remove them from the list. Else, flag > them to be deleted from the database. > if (holder.isNew()) { > _personHolders.remove(personPlus); > } > else { > holder.setDeleted(true); > } > } > > void onValidateForm() { > List<Person> personsToCreate = new ArrayList<Person>(); > List<Person> personsToChange = new ArrayList<Person>(); > List<Person> personsToDelete = new ArrayList<Person>(); > > for (PersonHolder holder : _personHolders) { > if (holder.isNew()) { > personsToCreate.add(holder.getPerson()); > } > else if (holder.isDeleted()) { > personsToDelete.add(holder.getPerson()); > } > else { > personsToChange.add(holder.getPerson()); > } > } > > System.out.println(">>> personsToCreate = " + personsToCreate); > System.out.println(">>> personsToChange = " + personsToChange); > System.out.println(">>> personsToDelete = " + personsToDelete); > > try { > // In a real application you would persist them to the > database instead of printing them > // getPersonService().bulkEditPersons(personsToCreate, > personsToChange, personsToDelete); > } > catch (Exception e) { > // Display the cause. In a real system we would try harder > to get a user-friendly message. > _form.recordError(ExceptionUtil.getRootCause(e)); > } > } > > Object onSuccess() { > List<Person> persons = new ArrayList<Person>(); > for (PersonHolder holder : _personHolders) { > if (!holder.isDeleted()) { > persons.add(holder.getPerson()); > } > } > _page2.set(persons); > endConversation(_conversationId); > _resources.discardPersistentFieldChanges(); > return _page2; > } > > void onRefresh() { > _resources.discardPersistentFieldChanges(); > onPrepare(); > } > > Object onActionFromGoHome() { > _resources.discardPersistentFieldChanges(); > return Index.class; > } > > @SuppressWarnings("unchecked") > public ValueEncoder getEncoder() { > return new ValueEncoder<PersonHolder>() { > > public String toClient(PersonHolder value) { > Long key = value.getKey(); > return key.toString(); > } > > public PersonHolder toValue(String keyAsString) { > Long key = new Long(keyAsString); > for (PersonHolder holder : _personHolders) { > if (holder.getKey().equals(key)) { > return holder; > } > } > throw new IllegalArgumentException("Received key \"" + key > + "\" which has no counterpart in this > collection: " + _personHolders); > } > }; > } > > public class PersonHolder { > private Person _person; > private Long _key; > private boolean _new; > private boolean _deleted; > > PersonHolder(Person person, boolean newPerson, Long key) { > _person = person; > _new = newPerson; > _key = key; > } > > public Person getPerson() { > return _person; > } > > public Long getKey() { > return _key; > } > > public boolean isNew() { > return _new; > } > > public boolean setDeleted(boolean deleted) { > return _deleted = deleted; > } > > public boolean isDeleted() { > return _deleted; > } > } > > private IPersonServiceLocal getPersonService() { > // Use our business services locator to get the EJB3 session > bean called "PersonServiceLocal". > return _businessServicesLocator.getPersonServiceLocal(); > } > > public Format getDateFormat() { > return DateFormat.getDateInstance(DateFormat.SHORT, > _currentLocale); > } > > private String startConversation(Object target) { > String conversationId = Long.toString(System.currentTimeMillis()); > _conversations.add(new Conversation(conversationId, target)); > return conversationId; > } > > private void endConversation(String conversationId) { > _conversations.remove(conversationId); > > // If conversations ASO is now empty then remove it from the > session > > if (_conversations.isEmpty()) { > _conversations = null; > } > } > > private List<PersonHolder> getPersonHoldersFromConversation(String > conversationId) { > Conversation conversation = _conversations.get(conversationId); > if (conversation != null&& conversation.getTarget() instanceof > List<?>) { > return (List<PersonHolder>) conversation.getTarget(); > } > return null; > } > > } > > > > --------------------------------------------------------------------- > To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org > For additional commands, e-mail: users-h...@tapestry.apache.org >