Hey everyone!  I'm new here and I apologize for not lurking long before
posting.  I have searched the archives extensively for this issue and have
come up empty, so I'm just going to jump in.

I'm working in Tapestry 5.1.0.5 (the latest as far as I know) and I've run
into a use-case that I can't figure out how to solve cleanly with T5.  I
actually used Tapestry extensively back in the 3.x and 4.x days, partially
because it could solve this particular use case very well when other
frameworks would fall flat.  Unfortunately, Tapestry works differently now,
so I think I need some help solving this in the new paradigm.  Also, this is
really long, because I wanted to include lots of code to give really good
context to my question.  Here goes:

I have two objects, let's call them Foo and Donkey (shout-out to the Java
Posse).  Each Foo object contains a Donkey object, like this:

public class Donkey {
    private String name;
    private String descr;
    // constructor & getters/setters go here
}

public class Foo {
    private String name;
    private Donkey donkey;
    // constructor & getters/setters go here
}

Now... the Donkey object represents user-contributed content.  Anywhere on
my site where I display a Donkey, I want to let the user edit the instance.
 To do this, I've created a reusable custom component that displays the
Donkey, including inline editing capabilities.  It's rudimentary now, but
eventually it'll probably use an Ajax modal popup from ChenilleKit.

==================ViewDonkeyInlineEdit.tml===============
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "
http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";>
<t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd";
xmlns:p="tapestry:parameter">
    Donkey's name: ${donkey.name}<br/>
    <t:if test="editing">
        <t:form>
            <t:errors/>
            Description: <t:textfield value="editDonkey.descr" size="50"/>
            <t:submit value="Save" event="saveEditDonkey"/> <t:eventlink
event="cancelEditDonkey">Cancel</t:eventlink><br/>
        </t:form>
        <p:else>
        Description: ${donkey.descr} <t:eventlink
event="startEditDonkey">Edit</t:eventlink><br/>
        </p:else>
    </t:if>
</t:container>
================ViewDonkeyInlineEdit.java=====================
public class ViewDonkeyInlineEdit
{
    @Property @Parameter private Donkey donkey;
    @Persist @Property private Donkey editDonkey;
    public boolean isEditing() {
        return (editDonkey!=null &&
editDonkey.getName().equals(donkey.getName()));
    }
    public void onStartEditDonkey() {
        editDonkey = new Donkey(donkey.getName(), donkey.getDescr());
    }
    public void onSaveEditDonkey() {
        donkey.setDescr(editDonkey.getDescr());
        editDonkey = null;
    }
    public void onCancelEditDonkey()  {
        editDonkey = null;
    }
}
==============

Now, let's use it in a page:
======== Test.tml ==============
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd";
      xmlns:p="tapestry:parameter">

    Here's a foo: ${firstFoo.name}<br/>
    <t:viewDonkeyInlineEdit donkey="firstFoo.donkey"/><br/><br/>

    Here's a foo: ${secondFoo.name}<br/>
    <t:viewDonkeyInlineEdit donkey="secondFoo.donkey"/><br/><br/>

    Here's a foo: ${thirdFoo.name}<br/>
    <t:viewDonkeyInlineEdit donkey="thirdFoo.donkey"/><br/>
</html>
=================

This works great.  No problems.

Clearly, though, this is *screaming* to be put into a loop.  So, let's try
this:

======= Test.tml (version 2) =========
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd";
       xmlns:p="tapestry:parameter">
    <t:loop source="fooList" value="fooLoopVar">
        Here's a foo: ${fooLoopVar.name}<br/>
        <t:viewDonkeyInlineEdit donkey="fooLoopVar.donkey"/><br/><br/>
    </t:loop>
</html>
==============

Of course, this doesn't work.  (If it did work, I wouldn't be writing this
message right now.)  When I click the "edit" link to triggger the
"startDonkeyEdit" event, I get a null pointer exception.

There are two things preventing this from working:
First, Tapestry does not replay the loop when processing the eventlink (like
it did in the 3.x and 4.x days).  Therefore, "fooLoopVar" never gets changed
from its default of null.
Second, there's only one instance of  the ViewDonkeyInlineEdit component
inside the loop.  All of the eventlink URLs look identical, so there's no
way to tell which Donkey should be edited.

I tried using the "context" parameter of the eventlink component, but since
events start at the deepest level and bubble up, I got the null pointer
exception before I could use the context for anything useful at the outer
(page) level.

Next, I tried adding an activation context to the page.

In Test.java, I added:
    String onPassivate() {
        return fooLoopVar==null?null:fooLoopVar.getName();
    }
    public void onActivate(String fooName) {
        for(Foo foo : list) {
            if(foo.getName().equals(fooName)) {
                this.fooLoopVar = foo;
            }
        }
    }

This worked really well.  It appears that the page's onPassivate() method is
called for each generation of eventlink, yielding a different URL each time.
 I thought I had found my solution!  Of course, if that were true I wouldn't
be writing this email. Because I'll be using this inline-editing component
throughout the site, loops will often occur embedded inside a few layers of
components instead of at the page level.  If you convert Test.java to a
component and put it into a page, then the activation context won't work any
more.  Activation contexts are only for pages.  Now I'm back to square one.
 :-(

Basically, I need something that works like the page activation context, but
generalized to work with any component at any level of the component tree.
 Maybe it could be something that bubbles all the info up to the page to be
put into a dynamic activation context, and then a mechanism to distribute
that information back to the components upon activation.

Any ideas?

Also note two other requirements:
1) The solution needs to handle multiple nested loops in multiple levers of
nested components.
2) The solution can't clutter the main code with lots of boilerplate.  I'll
be using it all over the place.

Thanks much for any ideas that anyone can offer!

-Nathan

Reply via email to