T5: ActionLink/Zone components inside a loop

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

T5: ActionLink/Zone components inside a loop

Travis McLeskey
When an ActionLink and Zone appear together in a loop like this:

<t:loop source="items" value="item">
   <t:actionlink zone="myzone">go!</t:actionlink>
   <t:zone t:id="myzone">in the zone?</t:zone>
   <br />
</t:loop>

Clicking the "go!" link from any iteration only affects the Zone from  
the first iteration. How do I connect each ActionLink to its  
corresponding Zone? I tried injecting the Zone into the java class and  
then using zone="${thezone.id}" in the actionlink, but then each  
ActionLink was connected to the Zone from the *previous* iteration.

Thanks!
Travis


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: T5: ActionLink/Zone components inside a loop

Howard Lewis Ship
Ajax constructs go outside of Tapestry's understanding of the page
construction.  It relies upon the developer to generate unique zone
ids.

In your situation, I would have the setItem() method generate a unique
zone id as a side effect and expose that as a proprty used by both the
ActionLink and the Zone.

On Feb 8, 2008 11:40 PM, Travis McLeskey <[hidden email]> wrote:

> When an ActionLink and Zone appear together in a loop like this:
>
> <t:loop source="items" value="item">
>    <t:actionlink zone="myzone">go!</t:actionlink>
>    <t:zone t:id="myzone">in the zone?</t:zone>
>    <br />
> </t:loop>
>
> Clicking the "go!" link from any iteration only affects the Zone from
> the first iteration. How do I connect each ActionLink to its
> corresponding Zone? I tried injecting the Zone into the java class and
> then using zone="${thezone.id}" in the actionlink, but then each
> ActionLink was connected to the Zone from the *previous* iteration.
>
> Thanks!
> Travis
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>



--
Howard M. Lewis Ship

Creator Apache Tapestry and Apache HiveMind

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: T5: ActionLink/Zone components inside a loop

Adriaan Joubert-3
Hi,

anybody had any luck with this? I tried

<t:actionlink zone="${zoneId}">Show</t:actionlink>
<t:zone visible="false" t:id="${zoneId}" context="${id}">
</t:zone>

But get

Component id '${zoneId}' is not valid; component ids must be valid Java
identifiers: start with a letter, and consist of letters, numbers and
underscores.

So it looks as if only literal values are accepted here?

And how do I deal with the events? The zone ids are constructed dynamically,
so I do not know what they will be when writing the code. I was hoping that
I could deal with this in an

void onEvent()

but I cannot get any context passed into this, so I do not know which zone
I'm dealing with. If anybody had an example of zones in a loop that would be
great!

Thanks,

Adriaan




On Feb 9, 2008 10:18 PM, Howard Lewis Ship <[hidden email]> wrote:

> Ajax constructs go outside of Tapestry's understanding of the page
> construction.  It relies upon the developer to generate unique zone
> ids.
>
> In your situation, I would have the setItem() method generate a unique
> zone id as a side effect and expose that as a proprty used by both the
> ActionLink and the Zone.
>
> On Feb 8, 2008 11:40 PM, Travis McLeskey <[hidden email]> wrote:
> > When an ActionLink and Zone appear together in a loop like this:
> >
> > <t:loop source="items" value="item">
> >    <t:actionlink zone="myzone">go!</t:actionlink>
> >    <t:zone t:id="myzone">in the zone?</t:zone>
> >    <br />
> > </t:loop>
> >
> > Clicking the "go!" link from any iteration only affects the Zone from
> > the first iteration. How do I connect each ActionLink to its
> > corresponding Zone? I tried injecting the Zone into the java class and
> > then using zone="${thezone.id}" in the actionlink, but then each
> > ActionLink was connected to the Zone from the *previous* iteration.
> >
> > Thanks!
> > Travis
> >
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: [hidden email]
> > For additional commands, e-mail: [hidden email]
> >
> >
>
>
>
> --
> Howard M. Lewis Ship
>
> Creator Apache Tapestry and Apache HiveMind
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>
Reply | Threaded
Open this post in threaded view
|

Re: T5: ActionLink/Zone components inside a loop

Olivier Jacquet
Not sure if this helps but I think that what you want is actually:

<t:actionlink t:zone="prop:zoneId">Shot</t:actionlink>

Regards,
Olivier


Adriaan Joubert wrote:

> Hi,
>
> anybody had any luck with this? I tried
>
> <t:actionlink zone="${zoneId}">Show</t:actionlink>
> <t:zone visible="false" t:id="${zoneId}" context="${id}">
> </t:zone>
>
> But get
>
> Component id '${zoneId}' is not valid; component ids must be valid Java
> identifiers: start with a letter, and consist of letters, numbers and
> underscores.
>
> So it looks as if only literal values are accepted here?
>
> And how do I deal with the events? The zone ids are constructed dynamically,
> so I do not know what they will be when writing the code. I was hoping that
> I could deal with this in an
>
> void onEvent()
>
> but I cannot get any context passed into this, so I do not know which zone
> I'm dealing with. If anybody had an example of zones in a loop that would be
> great!
>
> Thanks,
>
> Adriaan
>
>
>
>
> On Feb 9, 2008 10:18 PM, Howard Lewis Ship <[hidden email]> wrote:
>
>> Ajax constructs go outside of Tapestry's understanding of the page
>> construction.  It relies upon the developer to generate unique zone
>> ids.
>>
>> In your situation, I would have the setItem() method generate a unique
>> zone id as a side effect and expose that as a proprty used by both the
>> ActionLink and the Zone.
>>
>> On Feb 8, 2008 11:40 PM, Travis McLeskey <[hidden email]> wrote:
>>> When an ActionLink and Zone appear together in a loop like this:
>>>
>>> <t:loop source="items" value="item">
>>>    <t:actionlink zone="myzone">go!</t:actionlink>
>>>    <t:zone t:id="myzone">in the zone?</t:zone>
>>>    <br />
>>> </t:loop>
>>>
>>> Clicking the "go!" link from any iteration only affects the Zone from
>>> the first iteration. How do I connect each ActionLink to its
>>> corresponding Zone? I tried injecting the Zone into the java class and
>>> then using zone="${thezone.id}" in the actionlink, but then each
>>> ActionLink was connected to the Zone from the *previous* iteration.
>>>
>>> Thanks!
>>> Travis
>>>
>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: [hidden email]
>>> For additional commands, e-mail: [hidden email]
>>>
>>>
>>
>>
>> --
>> Howard M. Lewis Ship
>>
>> Creator Apache Tapestry and Apache HiveMind
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: [hidden email]
>> For additional commands, e-mail: [hidden email]
>>
>>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: T5: ActionLink/Zone components inside a loop (solved, sort of)

Travis McLeskey
In reply to this post by Travis McLeskey
(I wasn't subscribed to the list, so I'm sorry I'm not quoting the  
rest of the thread here.)

I ran into the same problem as Adriaan: it wouldn't let me use a  
property expansion for the zone's id attribute. The only way around  
this that I found was to create my own MyZone component (based on  
Tapestry's Zone.java) and add a "customId" attribute. Then, in  
MyZone.beginRender(), I replaced this:

         _clientId =  
_pageRenderSupport.allocateClientId(_resources.getId());

with something like:

         if( _resources.isBound("customId") )
             _clientId = _customId;
         else
             _clientId =  
_pageRenderSupport.allocateClientId(_resources.getId());

Then, I made my loop look more like this:

<t:loop source="items" value="item">
  <t:actionlink zone="myzone:${item.id}">go!</t:actionlink>
  <t:myzone customid="myzone:${item.id}">in the zone?</t:zone>
  <br />
</t:loop>

Which worked quite nicely, and it let me make a few other tweaks to  
how the Zone was rendered, like making it a <span> instead of a <div>.



However, that was only the first Zone-related hurdle. The next was  
that I couldn't find any examples in the documentation of how to  
actually provide the new content for the zone when the user clicks the  
link. After a lot of time digging through the code (and learning  
javascript!), I found the (or at least *a*) way to do it. I added this  
method to my class:

     public Object onActionFromUpdatezone(final long id) {
         JSONObject result = new JSONObject();
         result.put("content", "The new content for the Zone's <div>.  
Fresh from the server!");
         return result;
     }

(Note: I gave the ActionLink an id: "updatezone")



The next problem was that Zones in Tapestry currently can't do much  
other than query the server for new content, put that content in the  
<div>, and then call your "show" or "update" methods, if you specified  
them. You can't have it do something other than hit the server when  
the link is clicked, and you can't process the content before putting  
it in the <div>. Well, at least you can't do these things without the  
magic of javascript. My eventual solution is probably going to break  
in some future release of Tapestry, and it may provoke some frowns,  
but I circumvented all of the Zone-specific javascript code in  
Tapestry be redefining Tapestry.initializeZones. The javascript below  
is for an ActionLink that works as an expand/collapse button for the  
Zone. The first time you expand the zone, it downloads the content  
from the server and stores it in memory. After that, it doesn't need  
to hit the server again. Note that this code doesn't support the inner  
"t-zone-update" <div> that Tapestry's built-in javascript supports.


MyObj = {
   linkZone: function (link, zone) {
     zone = $(zone);
     link = $(link);
     var expanded = false;
     var origHTML = zone.innerHTML;
     var fullHTML;

     link.onclick = function(event) {
       if( expanded ) {
         zone.innerHTML = origHTML;
         link.innerHTML = "expand";
         expanded = false;
       } else {
         if( !zone.everPopulated ) {
           var successHandler = function(transport) {
             var response = transport.responseText;
             fullHTML = eval("(" + response + ")").content;
             zone.innerHTML = fullHTML;
           };
           var request = new Ajax.Request(link.href, { onSuccess :  
successHandler });
           zone.everPopulated = true;
         } else {
           zone.innerHTML = fullHTML;
         }
         link.innerHTML = "collapse"
         expanded = true;
       }
       return false;
     };
   }
};

Tapestry.initializeZones = function(zoneSpecs, linkSpecs) {
   $A(linkSpecs).each(function (spec)
   {
       MyObj.linkZone(spec[0],spec[1]);
   });
};



Hope that helps!
Travis





On Feb 8, 2008, at 11:40 PM, Travis McLeskey wrote:

> When an ActionLink and Zone appear together in a loop like this:
>
> <t:loop source="items" value="item">
>  <t:actionlink zone="myzone">go!</t:actionlink>
>  <t:zone t:id="myzone">in the zone?</t:zone>
>  <br />
> </t:loop>
>
> Clicking the "go!" link from any iteration only affects the Zone  
> from the first iteration. How do I connect each ActionLink to its  
> corresponding Zone? I tried injecting the Zone into the java class  
> and then using zone="${thezone.id}" in the actionlink, but then each  
> ActionLink was connected to the Zone from the *previous* iteration.
>
> Thanks!
> Travis
>


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: T5: ActionLink/Zone components inside a loop (solved, better)

Corin Lawson
Hi All,

I admire Travis' ambition, but he's forgetting an ancient programming maxim: be lazy! By that I mean there no need to implement your own Zone component (or sub-component). I actually got this to work, but I'm not %100 satisfied.

So the id attribute of zone doesn't allow property expansion; don't despair, use the default. The Loop component does 'unique-ify' the ids in a predictable manner, so let's take advantage of that! All we need is a method to compute what the zone id is expected to be for that iteration. This would be possible with render variables, if it weren't the fact that the second iteration starts at zero!

.tml snippet:
    <t:loop t:source="..." t:index="currentIndex" t:value="currentValue">
        <t:zone t:id="myZone">...${currentValue}...</t:zone>
        <t:actionlink t:id="myAction" t:zone="prop:currentZoneId" t:context="currentValue">...</t:actionlink>
    </t:loop>

.java snippet:
    @Component
    private Zone _myZone;

    private int _currentIndex; // Getters and Setters...
    private Object _currentValue; // Getters and Setters...

    public String getCurrentZoneId() {
        if(_currentIndex == 0)
            return "myZone";
        return "myZone_" + (_currentIndex - 1);
    }

    Object onAction(_currentValue) {
        setCurrentValue(_currentValue);
        return _myZone;
    }

Note that it is important to pass the currentValue through the context because I have used it's value inside the zone. Any property expansion inside the zone needs to passed through the context otherwise Tapestry (or more specifically the Zone component) doesn't know what iteration it is.

Limitations:

 (*) We are stuck with Loop's unique-ifation.
 (*) We must use the generic onAction event handler (unless the OnEvent annotation does property expansion in the component attribute(?))
 (*) Don't go crazy with the currentValue, stick to the primitives. Having said that you can always pass the currentIndex through the context and use that to set the currentValue.

There is a work-around these limitations. If with define an upper limit to the size of the Loop's source list (not an unreasonable thing to do) then we could ditch the context and be verbose to the event handler methods like so:

    Object onActionFromMyAction() {
        _currentIndex = 0;
        _currentValue = ...;
        return _myZone;
    }

    Object onActionFromMyAction_0() {
        _currentIndex = 1;
        _currentValue = ...;
        return _myZone;
    }

    Object onActionFromMyAction_1() {
        _currentIndex = 2;
        _currentValue = ...;
        return _myZone;
    }

etc.

In fact, it would be a fairly simple task to do this with java instrumentation. It would be even better to have a event type attribute on ActionLink, so that we can specify something other than action, like:

    Object onMyEventFromMyLink(currentIndex) {
        ...
        return _myZone;
    }

Dear Commiters, please add an event attribute to ActionLink.

Cheers,
Corin.

Travis McLeskey wrote
(I wasn't subscribed to the list, so I'm sorry I'm not quoting the  
rest of the thread here.)

I ran into the same problem as Adriaan: it wouldn't let me use a  
property expansion for the zone's id attribute. The only way around  
this that I found was to create my own MyZone component (based on  
Tapestry's Zone.java) and add a "customId" attribute. Then, in  
MyZone.beginRender(), I replaced this:

         _clientId =  
_pageRenderSupport.allocateClientId(_resources.getId());

with something like:

         if( _resources.isBound("customId") )
             _clientId = _customId;
         else
             _clientId =  
_pageRenderSupport.allocateClientId(_resources.getId());

Then, I made my loop look more like this:

<t:loop source="items" value="item">
  <t:actionlink zone="myzone:${item.id}">go!</t:actionlink>
  <t:myzone customid="myzone:${item.id}">in the zone?</t:zone>
  <br />
</t:loop>

Which worked quite nicely, and it let me make a few other tweaks to  
how the Zone was rendered, like making it a  instead of a <div>.



However, that was only the first Zone-related hurdle. The next was  
that I couldn't find any examples in the documentation of how to  
actually provide the new content for the zone when the user clicks the  
link. After a lot of time digging through the code (and learning  
javascript!), I found the (or at least *a*) way to do it. I added this  
method to my class:

     public Object onActionFromUpdatezone(final long id) {
         JSONObject result = new JSONObject();
         result.put("content", "The new content for the Zone's <div>.  
Fresh from the server!");
         return result;
     }

(Note: I gave the ActionLink an id: "updatezone")



The next problem was that Zones in Tapestry currently can't do much  
other than query the server for new content, put that content in the  
<div>, and then call your "show" or "update" methods, if you specified  
them. You can't have it do something other than hit the server when  
the link is clicked, and you can't process the content before putting  
it in the <div>. Well, at least you can't do these things without the  
magic of javascript. My eventual solution is probably going to break  
in some future release of Tapestry, and it may provoke some frowns,  
but I circumvented all of the Zone-specific javascript code in  
Tapestry be redefining Tapestry.initializeZones. The javascript below  
is for an ActionLink that works as an expand/collapse button for the  
Zone. The first time you expand the zone, it downloads the content  
from the server and stores it in memory. After that, it doesn't need  
to hit the server again. Note that this code doesn't support the inner  
"t-zone-update" <div> that Tapestry's built-in javascript supports.


MyObj = {
   linkZone: function (link, zone) {
     zone = $(zone);
     link = $(link);
     var expanded = false;
     var origHTML = zone.innerHTML;
     var fullHTML;

     link.onclick = function(event) {
       if( expanded ) {
         zone.innerHTML = origHTML;
         link.innerHTML = "expand";
         expanded = false;
       } else {
         if( !zone.everPopulated ) {
           var successHandler = function(transport) {
             var response = transport.responseText;
             fullHTML = eval("(" + response + ")").content;
             zone.innerHTML = fullHTML;
           };
           var request = new Ajax.Request(link.href, { onSuccess :  
successHandler });
           zone.everPopulated = true;
         } else {
           zone.innerHTML = fullHTML;
         }
         link.innerHTML = "collapse"
         expanded = true;
       }
       return false;
     };
   }
};

Tapestry.initializeZones = function(zoneSpecs, linkSpecs) {
   $A(linkSpecs).each(function (spec)
   {
       MyObj.linkZone(spec[0],spec[1]);
   });
};



Hope that helps!
Travis





On Feb 8, 2008, at 11:40 PM, Travis McLeskey wrote:

> When an ActionLink and Zone appear together in a loop like this:
>
> <t:loop source="items" value="item">
>  <t:actionlink zone="myzone">go!</t:actionlink>
>  <t:zone t:id="myzone">in the zone?</t:zone>
>  <br />
> </t:loop>
>
> Clicking the "go!" link from any iteration only affects the Zone  
> from the first iteration. How do I connect each ActionLink to its  
> corresponding Zone? I tried injecting the Zone into the java class  
> and then using zone="${thezone.id}" in the actionlink, but then each  
> ActionLink was connected to the Zone from the *previous* iteration.
>
> Thanks!
> Travis
>


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org
Reply | Threaded
Open this post in threaded view
|

Re: T5: ActionLink/Zone components inside a loop (solved, better)

Hugo Palma-4
Regarding your request for the event attribute in ActionLink. Doesn't
the component EventLink solve your problem ?

http://tapestry.apache.org/tapestry5/tapestry-core/ref/org/apache/tapestry/corelib/components/EventLink.html

Corin Lawson wrote:

> Hi All,
>
> I admire Travis' ambition, but he's forgetting an ancient programming maxim:
> be lazy! By that I mean there no need to implement your own Zone component
> (or sub-component). I actually got this to work, but I'm not %100 satisfied.
>
> So the id attribute of zone doesn't allow property expansion; don't despair,
> use the default. The Loop component does 'unique-ify' the ids in a
> predictable manner, so let's take advantage of that! All we need is a method
> to compute what the zone id is expected to be for that iteration. This would
> be possible with render variables, if it weren't the fact that the second
> iteration starts at zero!
>
> .tml snippet:
>     <t:loop t:source="..." t:index="currentIndex" t:value="currentValue">
>         <t:zone t:id="myZone">...${currentValue}...</t:zone>
>         <t:actionlink t:id="myAction" t:zone="prop:currentZoneId"
> t:context="currentValue">...</t:actionlink>
>     </t:loop>
>
> .java snippet:
>     @Component
>     private Zone _myZone;
>
>     private int _currentIndex; // Getters and Setters...
>     private Object _currentValue; // Getters and Setters...
>
>     public String getCurrentZoneId() {
>         if(_currentIndex == 0)
>             return "myZone";
>         return "myZone_" + (_currentIndex - 1);
>     }
>
>     Object onAction(_currentValue) {
>         setCurrentValue(_currentValue);
>         return _myZone;
>     }
>
> Note that it is important to pass the currentValue through the context
> because I have used it's value inside the zone. Any property expansion
> inside the zone needs to passed through the context otherwise Tapestry (or
> more specifically the Zone component) doesn't know what iteration it is.
>
> Limitations:
>
>  (*) We are stuck with Loop's unique-ifation.
>  (*) We must use the generic onAction event handler (unless the OnEvent
> annotation does property expansion in the component attribute(?))
>  (*) Don't go crazy with the currentValue, stick to the primitives. Having
> said that you can always pass the currentIndex through the context and use
> that to set the currentValue.
>
> There is a work-around these limitations. If with define an upper limit to
> the size of the Loop's source list (not an unreasonable thing to do) then we
> could ditch the context and be verbose to the event handler methods like so:
>
>     Object onActionFromMyAction() {
>         _currentIndex = 0;
>         _currentValue = ...;
>         return _myZone;
>     }
>
>     Object onActionFromMyAction_0() {
>         _currentIndex = 1;
>         _currentValue = ...;
>         return _myZone;
>     }
>
>     Object onActionFromMyAction_1() {
>         _currentIndex = 2;
>         _currentValue = ...;
>         return _myZone;
>     }
>
> etc.
>
> In fact, it would be a fairly simple task to do this with java
> instrumentation. It would be even better to have a event type attribute on
> ActionLink, so that we can specify something other than action, like:
>
>     Object onMyEventFromMyLink(currentIndex) {
>         ...
>         return _myZone;
>     }
>
> Dear Commiters, please add an event attribute to ActionLink.
>
> Cheers,
> Corin.
>
>
> Travis McLeskey wrote:
>  
>> (I wasn't subscribed to the list, so I'm sorry I'm not quoting the  
>> rest of the thread here.)
>>
>> I ran into the same problem as Adriaan: it wouldn't let me use a  
>> property expansion for the zone's id attribute. The only way around  
>> this that I found was to create my own MyZone component (based on  
>> Tapestry's Zone.java) and add a "customId" attribute. Then, in  
>> MyZone.beginRender(), I replaced this:
>>
>>          _clientId =  
>> _pageRenderSupport.allocateClientId(_resources.getId());
>>
>> with something like:
>>
>>          if( _resources.isBound("customId") )
>>              _clientId = _customId;
>>          else
>>              _clientId =  
>> _pageRenderSupport.allocateClientId(_resources.getId());
>>
>> Then, I made my loop look more like this:
>>
>> <t:loop source="items" value="item">
>>   <t:actionlink zone="myzone:${item.id}">go!</t:actionlink>
>>   <t:myzone customid="myzone:${item.id}">in the zone?</t:zone>
>>   <br />
>> </t:loop>
>>
>> Which worked quite nicely, and it let me make a few other tweaks to  
>> how the Zone was rendered, like making it a  instead of a <div>.
>>
>>
>>
>> However, that was only the first Zone-related hurdle. The next was  
>> that I couldn't find any examples in the documentation of how to  
>> actually provide the new content for the zone when the user clicks the  
>> link. After a lot of time digging through the code (and learning  
>> javascript!), I found the (or at least *a*) way to do it. I added this  
>> method to my class:
>>
>>      public Object onActionFromUpdatezone(final long id) {
>>          JSONObject result = new JSONObject();
>>          result.put("content", "The new content for the Zone's <div>.  
>> Fresh from the server!");
>>          return result;
>>      }
>>
>> (Note: I gave the ActionLink an id: "updatezone")
>>
>>
>>
>> The next problem was that Zones in Tapestry currently can't do much  
>> other than query the server for new content, put that content in the  
>> <div>, and then call your "show" or "update" methods, if you specified  
>> them. You can't have it do something other than hit the server when  
>> the link is clicked, and you can't process the content before putting  
>> it in the <div>. Well, at least you can't do these things without the  
>> magic of javascript. My eventual solution is probably going to break  
>> in some future release of Tapestry, and it may provoke some frowns,  
>> but I circumvented all of the Zone-specific javascript code in  
>> Tapestry be redefining Tapestry.initializeZones. The javascript below  
>> is for an ActionLink that works as an expand/collapse button for the  
>> Zone. The first time you expand the zone, it downloads the content  
>> from the server and stores it in memory. After that, it doesn't need  
>> to hit the server again. Note that this code doesn't support the inner  
>> "t-zone-update" <div> that Tapestry's built-in javascript supports.
>>
>>
>> MyObj = {
>>    linkZone: function (link, zone) {
>>      zone = $(zone);
>>      link = $(link);
>>      var expanded = false;
>>      var origHTML = zone.innerHTML;
>>      var fullHTML;
>>
>>      link.onclick = function(event) {
>>        if( expanded ) {
>>          zone.innerHTML = origHTML;
>>          link.innerHTML = "expand";
>>          expanded = false;
>>        } else {
>>          if( !zone.everPopulated ) {
>>            var successHandler = function(transport) {
>>              var response = transport.responseText;
>>              fullHTML = eval("(" + response + ")").content;
>>              zone.innerHTML = fullHTML;
>>            };
>>            var request = new Ajax.Request(link.href, { onSuccess :  
>> successHandler });
>>            zone.everPopulated = true;
>>          } else {
>>            zone.innerHTML = fullHTML;
>>          }
>>          link.innerHTML = "collapse"
>>          expanded = true;
>>        }
>>        return false;
>>      };
>>    }
>> };
>>
>> Tapestry.initializeZones = function(zoneSpecs, linkSpecs) {
>>    $A(linkSpecs).each(function (spec)
>>    {
>>        MyObj.linkZone(spec[0],spec[1]);
>>    });
>> };
>>
>>
>>
>> Hope that helps!
>> Travis
>>
>>
>>
>>
>>
>> On Feb 8, 2008, at 11:40 PM, Travis McLeskey wrote:
>>
>>    
>>> When an ActionLink and Zone appear together in a loop like this:
>>>
>>> <t:loop source="items" value="item">
>>>  <t:actionlink zone="myzone">go!</t:actionlink>
>>>  <t:zone t:id="myzone">in the zone?</t:zone>
>>>  <br />
>>> </t:loop>
>>>
>>> Clicking the "go!" link from any iteration only affects the Zone  
>>> from the first iteration. How do I connect each ActionLink to its  
>>> corresponding Zone? I tried injecting the Zone into the java class  
>>> and then using zone="${thezone.id}" in the actionlink, but then each  
>>> ActionLink was connected to the Zone from the *previous* iteration.
>>>
>>> Thanks!
>>> Travis
>>>
>>>      
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: [hidden email]
>> For additional commands, e-mail: [hidden email]
>>
>>
>>
>>    
>
>  
Reply | Threaded
Open this post in threaded view
|

Re: T5: ActionLink/Zone components inside a loop (solved, better)

Corin Lawson
Awesome!

Sorry, I overlooked that one ... I'm just a noob :P

Thanks for sharing HugoPalma.

All that I want now is the ability do away with the currentZoneId property. Could someone explain the component prefix of a binding expression. The Component Parameters page of the tapestry-core guide simply says:

'The id of another component within the same template'

Could someone provide an example of how it's used?

HugoPalma wrote
Regarding your request for the event attribute in ActionLink. Doesn't
the component EventLink solve your problem ?

http://tapestry.apache.org/tapestry5/tapestry-core/ref/org/apache/tapestry/corelib/components/EventLink.html

Corin Lawson wrote:
> Hi All,
>
> I admire Travis' ambition, but he's forgetting an ancient programming maxim:
> be lazy! By that I mean there no need to implement your own Zone component
> (or sub-component). I actually got this to work, but I'm not %100 satisfied.
>
> So the id attribute of zone doesn't allow property expansion; don't despair,
> use the default. The Loop component does 'unique-ify' the ids in a
> predictable manner, so let's take advantage of that! All we need is a method
> to compute what the zone id is expected to be for that iteration. This would
> be possible with render variables, if it weren't the fact that the second
> iteration starts at zero!
>
> .tml snippet:
>     <t:loop t:source="..." t:index="currentIndex" t:value="currentValue">
>         <t:zone t:id="myZone">...${currentValue}...</t:zone>
>         <t:actionlink t:id="myAction" t:zone="prop:currentZoneId"
> t:context="currentValue">...</t:actionlink>
>     </t:loop>
>
> .java snippet:
>     @Component
>     private Zone _myZone;
>
>     private int _currentIndex; // Getters and Setters...
>     private Object _currentValue; // Getters and Setters...
>
>     public String getCurrentZoneId() {
>         if(_currentIndex == 0)
>             return "myZone";
>         return "myZone_" + (_currentIndex - 1);
>     }
>
>     Object onAction(_currentValue) {
>         setCurrentValue(_currentValue);
>         return _myZone;
>     }
>
> Note that it is important to pass the currentValue through the context
> because I have used it's value inside the zone. Any property expansion
> inside the zone needs to passed through the context otherwise Tapestry (or
> more specifically the Zone component) doesn't know what iteration it is.
>
> Limitations:
>
>  (*) We are stuck with Loop's unique-ifation.
>  (*) We must use the generic onAction event handler (unless the OnEvent
> annotation does property expansion in the component attribute(?))
>  (*) Don't go crazy with the currentValue, stick to the primitives. Having
> said that you can always pass the currentIndex through the context and use
> that to set the currentValue.
>
> There is a work-around these limitations. If with define an upper limit to
> the size of the Loop's source list (not an unreasonable thing to do) then we
> could ditch the context and be verbose to the event handler methods like so:
>
>     Object onActionFromMyAction() {
>         _currentIndex = 0;
>         _currentValue = ...;
>         return _myZone;
>     }
>
>     Object onActionFromMyAction_0() {
>         _currentIndex = 1;
>         _currentValue = ...;
>         return _myZone;
>     }
>
>     Object onActionFromMyAction_1() {
>         _currentIndex = 2;
>         _currentValue = ...;
>         return _myZone;
>     }
>
> etc.
>
> In fact, it would be a fairly simple task to do this with java
> instrumentation. It would be even better to have a event type attribute on
> ActionLink, so that we can specify something other than action, like:
>
>     Object onMyEventFromMyLink(currentIndex) {
>         ...
>         return _myZone;
>     }
>
> Dear Commiters, please add an event attribute to ActionLink.
>
> Cheers,
> Corin.
>
>
> Travis McLeskey wrote:
>  
>> (I wasn't subscribed to the list, so I'm sorry I'm not quoting the  
>> rest of the thread here.)
>>
>> I ran into the same problem as Adriaan: it wouldn't let me use a  
>> property expansion for the zone's id attribute. The only way around  
>> this that I found was to create my own MyZone component (based on  
>> Tapestry's Zone.java) and add a "customId" attribute. Then, in  
>> MyZone.beginRender(), I replaced this:
>>
>>          _clientId =  
>> _pageRenderSupport.allocateClientId(_resources.getId());
>>
>> with something like:
>>
>>          if( _resources.isBound("customId") )
>>              _clientId = _customId;
>>          else
>>              _clientId =  
>> _pageRenderSupport.allocateClientId(_resources.getId());
>>
>> Then, I made my loop look more like this:
>>
>> <t:loop source="items" value="item">
>>   <t:actionlink zone="myzone:${item.id}">go!</t:actionlink>
>>   <t:myzone customid="myzone:${item.id}">in the zone?</t:zone>
>>   <br />
>> </t:loop>
>>
>> Which worked quite nicely, and it let me make a few other tweaks to  
>> how the Zone was rendered, like making it a  instead of a <div>.
>>
>>
>>
>> However, that was only the first Zone-related hurdle. The next was  
>> that I couldn't find any examples in the documentation of how to  
>> actually provide the new content for the zone when the user clicks the  
>> link. After a lot of time digging through the code (and learning  
>> javascript!), I found the (or at least *a*) way to do it. I added this  
>> method to my class:
>>
>>      public Object onActionFromUpdatezone(final long id) {
>>          JSONObject result = new JSONObject();
>>          result.put("content", "The new content for the Zone's <div>.  
>> Fresh from the server!");
>>          return result;
>>      }
>>
>> (Note: I gave the ActionLink an id: "updatezone")
>>
>>
>>
>> The next problem was that Zones in Tapestry currently can't do much  
>> other than query the server for new content, put that content in the  
>> <div>, and then call your "show" or "update" methods, if you specified  
>> them. You can't have it do something other than hit the server when  
>> the link is clicked, and you can't process the content before putting  
>> it in the <div>. Well, at least you can't do these things without the  
>> magic of javascript. My eventual solution is probably going to break  
>> in some future release of Tapestry, and it may provoke some frowns,  
>> but I circumvented all of the Zone-specific javascript code in  
>> Tapestry be redefining Tapestry.initializeZones. The javascript below  
>> is for an ActionLink that works as an expand/collapse button for the  
>> Zone. The first time you expand the zone, it downloads the content  
>> from the server and stores it in memory. After that, it doesn't need  
>> to hit the server again. Note that this code doesn't support the inner  
>> "t-zone-update" <div> that Tapestry's built-in javascript supports.
>>
>>
>> MyObj = {
>>    linkZone: function (link, zone) {
>>      zone = $(zone);
>>      link = $(link);
>>      var expanded = false;
>>      var origHTML = zone.innerHTML;
>>      var fullHTML;
>>
>>      link.onclick = function(event) {
>>        if( expanded ) {
>>          zone.innerHTML = origHTML;
>>          link.innerHTML = "expand";
>>          expanded = false;
>>        } else {
>>          if( !zone.everPopulated ) {
>>            var successHandler = function(transport) {
>>              var response = transport.responseText;
>>              fullHTML = eval("(" + response + ")").content;
>>              zone.innerHTML = fullHTML;
>>            };
>>            var request = new Ajax.Request(link.href, { onSuccess :  
>> successHandler });
>>            zone.everPopulated = true;
>>          } else {
>>            zone.innerHTML = fullHTML;
>>          }
>>          link.innerHTML = "collapse"
>>          expanded = true;
>>        }
>>        return false;
>>      };
>>    }
>> };
>>
>> Tapestry.initializeZones = function(zoneSpecs, linkSpecs) {
>>    $A(linkSpecs).each(function (spec)
>>    {
>>        MyObj.linkZone(spec[0],spec[1]);
>>    });
>> };
>>
>>
>>
>> Hope that helps!
>> Travis
>>
>>
>>
>>
>>
>> On Feb 8, 2008, at 11:40 PM, Travis McLeskey wrote:
>>
>>    
>>> When an ActionLink and Zone appear together in a loop like this:
>>>
>>> <t:loop source="items" value="item">
>>>  <t:actionlink zone="myzone">go!</t:actionlink>
>>>  <t:zone t:id="myzone">in the zone?</t:zone>
>>>  <br />
>>> </t:loop>
>>>
>>> Clicking the "go!" link from any iteration only affects the Zone  
>>> from the first iteration. How do I connect each ActionLink to its  
>>> corresponding Zone? I tried injecting the Zone into the java class  
>>> and then using zone="${thezone.id}" in the actionlink, but then each  
>>> ActionLink was connected to the Zone from the *previous* iteration.
>>>
>>> Thanks!
>>> Travis
>>>
>>>      
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
>> For additional commands, e-mail: users-help@tapestry.apache.org
>>
>>
>>
>>    
>
>  
Reply | Threaded
Open this post in threaded view
|

Re: T5: ActionLink/Zone components inside a loop (solved, better)

Hugo Palma-4
No problem, just glad i could help :o)

Regarding the component prefix, have you tried using it ? Because it
really is just what that line says. You just have to provide the id of a
component and the expression will resolve to the component instance.
Example:

<t:pagelink t:id="mypagelink" page="Start">Home</t:pagelink>

<t:delegate to="component:mypagelink"/>

This would render the pagelink component twice.

Corin Lawson wrote:

> Awesome!
>
> Sorry, I overlooked that one ... I'm just a noob :P
>
> Thanks for sharing HugoPalma.
>
> All that I want now is the ability do away with the currentZoneId property.
> Could someone explain the component prefix of a binding expression. The
> Component Parameters page of the tapestry-core guide simply says:
>
> 'The id of another component within the same template'
>
> Could someone provide an example of how it's used?
>
>
> HugoPalma wrote:
>  
>> Regarding your request for the event attribute in ActionLink. Doesn't
>> the component EventLink solve your problem ?
>>
>> http://tapestry.apache.org/tapestry5/tapestry-core/ref/org/apache/tapestry/corelib/components/EventLink.html
>>
>> Corin Lawson wrote:
>>    
>>> Hi All,
>>>
>>> I admire Travis' ambition, but he's forgetting an ancient programming
>>> maxim:
>>> be lazy! By that I mean there no need to implement your own Zone
>>> component
>>> (or sub-component). I actually got this to work, but I'm not %100
>>> satisfied.
>>>
>>> So the id attribute of zone doesn't allow property expansion; don't
>>> despair,
>>> use the default. The Loop component does 'unique-ify' the ids in a
>>> predictable manner, so let's take advantage of that! All we need is a
>>> method
>>> to compute what the zone id is expected to be for that iteration. This
>>> would
>>> be possible with render variables, if it weren't the fact that the second
>>> iteration starts at zero!
>>>
>>> .tml snippet:
>>>     <t:loop t:source="..." t:index="currentIndex" t:value="currentValue">
>>>         <t:zone t:id="myZone">...${currentValue}...</t:zone>
>>>         <t:actionlink t:id="myAction" t:zone="prop:currentZoneId"
>>> t:context="currentValue">...</t:actionlink>
>>>     </t:loop>
>>>
>>> .java snippet:
>>>     @Component
>>>     private Zone _myZone;
>>>
>>>     private int _currentIndex; // Getters and Setters...
>>>     private Object _currentValue; // Getters and Setters...
>>>
>>>     public String getCurrentZoneId() {
>>>         if(_currentIndex == 0)
>>>             return "myZone";
>>>         return "myZone_" + (_currentIndex - 1);
>>>     }
>>>
>>>     Object onAction(_currentValue) {
>>>         setCurrentValue(_currentValue);
>>>         return _myZone;
>>>     }
>>>
>>> Note that it is important to pass the currentValue through the context
>>> because I have used it's value inside the zone. Any property expansion
>>> inside the zone needs to passed through the context otherwise Tapestry
>>> (or
>>> more specifically the Zone component) doesn't know what iteration it is.
>>>
>>> Limitations:
>>>
>>>  (*) We are stuck with Loop's unique-ifation.
>>>  (*) We must use the generic onAction event handler (unless the OnEvent
>>> annotation does property expansion in the component attribute(?))
>>>  (*) Don't go crazy with the currentValue, stick to the primitives.
>>> Having
>>> said that you can always pass the currentIndex through the context and
>>> use
>>> that to set the currentValue.
>>>
>>> There is a work-around these limitations. If with define an upper limit
>>> to
>>> the size of the Loop's source list (not an unreasonable thing to do) then
>>> we
>>> could ditch the context and be verbose to the event handler methods like
>>> so:
>>>
>>>     Object onActionFromMyAction() {
>>>         _currentIndex = 0;
>>>         _currentValue = ...;
>>>         return _myZone;
>>>     }
>>>
>>>     Object onActionFromMyAction_0() {
>>>         _currentIndex = 1;
>>>         _currentValue = ...;
>>>         return _myZone;
>>>     }
>>>
>>>     Object onActionFromMyAction_1() {
>>>         _currentIndex = 2;
>>>         _currentValue = ...;
>>>         return _myZone;
>>>     }
>>>
>>> etc.
>>>
>>> In fact, it would be a fairly simple task to do this with java
>>> instrumentation. It would be even better to have a event type attribute
>>> on
>>> ActionLink, so that we can specify something other than action, like:
>>>
>>>     Object onMyEventFromMyLink(currentIndex) {
>>>         ...
>>>         return _myZone;
>>>     }
>>>
>>> Dear Commiters, please add an event attribute to ActionLink.
>>>
>>> Cheers,
>>> Corin.
>>>
>>>
>>> Travis McLeskey wrote:
>>>  
>>>      
>>>> (I wasn't subscribed to the list, so I'm sorry I'm not quoting the  
>>>> rest of the thread here.)
>>>>
>>>> I ran into the same problem as Adriaan: it wouldn't let me use a  
>>>> property expansion for the zone's id attribute. The only way around  
>>>> this that I found was to create my own MyZone component (based on  
>>>> Tapestry's Zone.java) and add a "customId" attribute. Then, in  
>>>> MyZone.beginRender(), I replaced this:
>>>>
>>>>          _clientId =  
>>>> _pageRenderSupport.allocateClientId(_resources.getId());
>>>>
>>>> with something like:
>>>>
>>>>          if( _resources.isBound("customId") )
>>>>              _clientId = _customId;
>>>>          else
>>>>              _clientId =  
>>>> _pageRenderSupport.allocateClientId(_resources.getId());
>>>>
>>>> Then, I made my loop look more like this:
>>>>
>>>> <t:loop source="items" value="item">
>>>>   <t:actionlink zone="myzone:${item.id}">go!</t:actionlink>
>>>>   <t:myzone customid="myzone:${item.id}">in the zone?</t:zone>
>>>>   <br />
>>>> </t:loop>
>>>>
>>>> Which worked quite nicely, and it let me make a few other tweaks to  
>>>> how the Zone was rendered, like making it a  instead of a <div>.
>>>>
>>>>
>>>>
>>>> However, that was only the first Zone-related hurdle. The next was  
>>>> that I couldn't find any examples in the documentation of how to  
>>>> actually provide the new content for the zone when the user clicks the  
>>>> link. After a lot of time digging through the code (and learning  
>>>> javascript!), I found the (or at least *a*) way to do it. I added this  
>>>> method to my class:
>>>>
>>>>      public Object onActionFromUpdatezone(final long id) {
>>>>          JSONObject result = new JSONObject();
>>>>          result.put("content", "The new content for the Zone's <div>.  
>>>> Fresh from the server!");
>>>>          return result;
>>>>      }
>>>>
>>>> (Note: I gave the ActionLink an id: "updatezone")
>>>>
>>>>
>>>>
>>>> The next problem was that Zones in Tapestry currently can't do much  
>>>> other than query the server for new content, put that content in the  
>>>> <div>, and then call your "show" or "update" methods, if you specified  
>>>> them. You can't have it do something other than hit the server when  
>>>> the link is clicked, and you can't process the content before putting  
>>>> it in the <div>. Well, at least you can't do these things without the  
>>>> magic of javascript. My eventual solution is probably going to break  
>>>> in some future release of Tapestry, and it may provoke some frowns,  
>>>> but I circumvented all of the Zone-specific javascript code in  
>>>> Tapestry be redefining Tapestry.initializeZones. The javascript below  
>>>> is for an ActionLink that works as an expand/collapse button for the  
>>>> Zone. The first time you expand the zone, it downloads the content  
>>>> from the server and stores it in memory. After that, it doesn't need  
>>>> to hit the server again. Note that this code doesn't support the inner  
>>>> "t-zone-update" <div> that Tapestry's built-in javascript supports.
>>>>
>>>>
>>>> MyObj = {
>>>>    linkZone: function (link, zone) {
>>>>      zone = $(zone);
>>>>      link = $(link);
>>>>      var expanded = false;
>>>>      var origHTML = zone.innerHTML;
>>>>      var fullHTML;
>>>>
>>>>      link.onclick = function(event) {
>>>>        if( expanded ) {
>>>>          zone.innerHTML = origHTML;
>>>>          link.innerHTML = "expand";
>>>>          expanded = false;
>>>>        } else {
>>>>          if( !zone.everPopulated ) {
>>>>            var successHandler = function(transport) {
>>>>              var response = transport.responseText;
>>>>              fullHTML = eval("(" + response + ")").content;
>>>>              zone.innerHTML = fullHTML;
>>>>            };
>>>>            var request = new Ajax.Request(link.href, { onSuccess :  
>>>> successHandler });
>>>>            zone.everPopulated = true;
>>>>          } else {
>>>>            zone.innerHTML = fullHTML;
>>>>          }
>>>>          link.innerHTML = "collapse"
>>>>          expanded = true;
>>>>        }
>>>>        return false;
>>>>      };
>>>>    }
>>>> };
>>>>
>>>> Tapestry.initializeZones = function(zoneSpecs, linkSpecs) {
>>>>    $A(linkSpecs).each(function (spec)
>>>>    {
>>>>        MyObj.linkZone(spec[0],spec[1]);
>>>>    });
>>>> };
>>>>
>>>>
>>>>
>>>> Hope that helps!
>>>> Travis
>>>>
>>>>
>>>>
>>>>
>>>>
>>>> On Feb 8, 2008, at 11:40 PM, Travis McLeskey wrote:
>>>>
>>>>    
>>>>        
>>>>> When an ActionLink and Zone appear together in a loop like this:
>>>>>
>>>>> <t:loop source="items" value="item">
>>>>>  <t:actionlink zone="myzone">go!</t:actionlink>
>>>>>  <t:zone t:id="myzone">in the zone?</t:zone>
>>>>>  <br />
>>>>> </t:loop>
>>>>>
>>>>> Clicking the "go!" link from any iteration only affects the Zone  
>>>>> from the first iteration. How do I connect each ActionLink to its  
>>>>> corresponding Zone? I tried injecting the Zone into the java class  
>>>>> and then using zone="${thezone.id}" in the actionlink, but then each  
>>>>> ActionLink was connected to the Zone from the *previous* iteration.
>>>>>
>>>>> Thanks!
>>>>> Travis
>>>>>
>>>>>      
>>>>>          
>>>> ---------------------------------------------------------------------
>>>> To unsubscribe, e-mail: [hidden email]
>>>> For additional commands, e-mail: [hidden email]
>>>>
>>>>
>>>>
>>>>    
>>>>        
>>>  
>>>      
>>    
>
>  
Reply | Threaded
Open this post in threaded view
|

Re: T5: ActionLink/Zone components inside a loop (solved, better)

Corin Lawson
This post was updated on .
Oh I get it now...

I tried using "${component:myZone}.id" as the zone attribute of the link but all the variations I tried just rendered something like this:

"org.apache.tapestry.corelib.components.Zone@f891c.id"

I couldn't work out how to access properties of the component. Any ideas?

But it gave me another idea to free my code from getCurrentZoneId method. I am now significantly satisfied with the following solution. It makes for one very lean POJO.

 .tml snippet:
     <t:loop t:source="myList" t:index="var:index" t:value="currentValue">
         <t:zone t:id="myZone">...${currentValue}...</t:zone>
         <t:eventlink t:event="myUpdate" t:zone="prop:myZone.clientId" t:context="var:index">...</t:actionlink>
     </t:loop>

 .java snippet:
     @Component
     private Zone _myZone; // Getters and Setters...
     private List _myList; // Getters and Setters...
     private Object _currentValue; // Getters and Setters...

     Object onMyUpdate(index) {
         setCurrentValue(_myList.get(index));
         return _myZone;
     }

QED

Cheers,
Corin

HugoPalma wrote
No problem, just glad i could help :o)

Regarding the component prefix, have you tried using it ? Because it
really is just what that line says. You just have to provide the id of a
component and the expression will resolve to the component instance.
Example:

<t:pagelink t:id="mypagelink" page="Start">Home</t:pagelink>

<t:delegate to="component:mypagelink"/>

This would render the pagelink component twice.

Corin Lawson wrote:
> Awesome!
>
> Sorry, I overlooked that one ... I'm just a noob :P
>
> Thanks for sharing HugoPalma.
>
> All that I want now is the ability do away with the currentZoneId property.
> Could someone explain the component prefix of a binding expression. The
> Component Parameters page of the tapestry-core guide simply says:
>
> 'The id of another component within the same template'
>
> Could someone provide an example of how it's used?
>
>
> HugoPalma wrote:
>  
>> Regarding your request for the event attribute in ActionLink. Doesn't
>> the component EventLink solve your problem ?
>>
>> http://tapestry.apache.org/tapestry5/tapestry-core/ref/org/apache/tapestry/corelib/components/EventLink.html
>>
>> Corin Lawson wrote:
>>    
>>> Hi All,
>>>
>>> I admire Travis' ambition, but he's forgetting an ancient programming
>>> maxim:
>>> be lazy! By that I mean there no need to implement your own Zone
>>> component
>>> (or sub-component). I actually got this to work, but I'm not %100
>>> satisfied.
>>>
>>> So the id attribute of zone doesn't allow property expansion; don't
>>> despair,
>>> use the default. The Loop component does 'unique-ify' the ids in a
>>> predictable manner, so let's take advantage of that! All we need is a
>>> method
>>> to compute what the zone id is expected to be for that iteration. This
>>> would
>>> be possible with render variables, if it weren't the fact that the second
>>> iteration starts at zero!
>>>
>>> .tml snippet:
>>>     <t:loop t:source="..." t:index="currentIndex" t:value="currentValue">
>>>         <t:zone t:id="myZone">...${currentValue}...</t:zone>
>>>         <t:actionlink t:id="myAction" t:zone="prop:currentZoneId"
>>> t:context="currentValue">...</t:actionlink>
>>>     </t:loop>
>>>
>>> .java snippet:
>>>     @Component
>>>     private Zone _myZone;
>>>
>>>     private int _currentIndex; // Getters and Setters...
>>>     private Object _currentValue; // Getters and Setters...
>>>
>>>     public String getCurrentZoneId() {
>>>         if(_currentIndex == 0)
>>>             return "myZone";
>>>         return "myZone_" + (_currentIndex - 1);
>>>     }
>>>
>>>     Object onAction(_currentValue) {
>>>         setCurrentValue(_currentValue);
>>>         return _myZone;
>>>     }
>>>
>>> Note that it is important to pass the currentValue through the context
>>> because I have used it's value inside the zone. Any property expansion
>>> inside the zone needs to passed through the context otherwise Tapestry
>>> (or
>>> more specifically the Zone component) doesn't know what iteration it is.
>>>
>>> Limitations:
>>>
>>>  (*) We are stuck with Loop's unique-ifation.
>>>  (*) We must use the generic onAction event handler (unless the OnEvent
>>> annotation does property expansion in the component attribute(?))
>>>  (*) Don't go crazy with the currentValue, stick to the primitives.
>>> Having
>>> said that you can always pass the currentIndex through the context and
>>> use
>>> that to set the currentValue.
>>>
>>> There is a work-around these limitations. If with define an upper limit
>>> to
>>> the size of the Loop's source list (not an unreasonable thing to do) then
>>> we
>>> could ditch the context and be verbose to the event handler methods like
>>> so:
>>>
>>>     Object onActionFromMyAction() {
>>>         _currentIndex = 0;
>>>         _currentValue = ...;
>>>         return _myZone;
>>>     }
>>>
>>>     Object onActionFromMyAction_0() {
>>>         _currentIndex = 1;
>>>         _currentValue = ...;
>>>         return _myZone;
>>>     }
>>>
>>>     Object onActionFromMyAction_1() {
>>>         _currentIndex = 2;
>>>         _currentValue = ...;
>>>         return _myZone;
>>>     }
>>>
>>> etc.
>>>
>>> In fact, it would be a fairly simple task to do this with java
>>> instrumentation. It would be even better to have a event type attribute
>>> on
>>> ActionLink, so that we can specify something other than action, like:
>>>
>>>     Object onMyEventFromMyLink(currentIndex) {
>>>         ...
>>>         return _myZone;
>>>     }
>>>
>>> Dear Commiters, please add an event attribute to ActionLink.
>>>
>>> Cheers,
>>> Corin.
>>>
>>>
>>> Travis McLeskey wrote:
>>>  
>>>      
>>>> (I wasn't subscribed to the list, so I'm sorry I'm not quoting the  
>>>> rest of the thread here.)
>>>>
>>>> I ran into the same problem as Adriaan: it wouldn't let me use a  
>>>> property expansion for the zone's id attribute. The only way around  
>>>> this that I found was to create my own MyZone component (based on  
>>>> Tapestry's Zone.java) and add a "customId" attribute. Then, in  
>>>> MyZone.beginRender(), I replaced this:
>>>>
>>>>          _clientId =  
>>>> _pageRenderSupport.allocateClientId(_resources.getId());
>>>>
>>>> with something like:
>>>>
>>>>          if( _resources.isBound("customId") )
>>>>              _clientId = _customId;
>>>>          else
>>>>              _clientId =  
>>>> _pageRenderSupport.allocateClientId(_resources.getId());
>>>>
>>>> Then, I made my loop look more like this:
>>>>
>>>> <t:loop source="items" value="item">
>>>>   <t:actionlink zone="myzone:${item.id}">go!</t:actionlink>
>>>>   <t:myzone customid="myzone:${item.id}">in the zone?</t:zone>
>>>>   <br />
>>>> </t:loop>
>>>>
>>>> Which worked quite nicely, and it let me make a few other tweaks to  
>>>> how the Zone was rendered, like making it a  instead of a <div>.
>>>>
>>>>
>>>>
>>>> However, that was only the first Zone-related hurdle. The next was  
>>>> that I couldn't find any examples in the documentation of how to  
>>>> actually provide the new content for the zone when the user clicks the  
>>>> link. After a lot of time digging through the code (and learning  
>>>> javascript!), I found the (or at least *a*) way to do it. I added this  
>>>> method to my class:
>>>>
>>>>      public Object onActionFromUpdatezone(final long id) {
>>>>          JSONObject result = new JSONObject();
>>>>          result.put("content", "The new content for the Zone's <div>.  
>>>> Fresh from the server!");
>>>>          return result;
>>>>      }
>>>>
>>>> (Note: I gave the ActionLink an id: "updatezone")
>>>>
>>>>
>>>>
>>>> The next problem was that Zones in Tapestry currently can't do much  
>>>> other than query the server for new content, put that content in the  
>>>> <div>, and then call your "show" or "update" methods, if you specified  
>>>> them. You can't have it do something other than hit the server when  
>>>> the link is clicked, and you can't process the content before putting  
>>>> it in the <div>. Well, at least you can't do these things without the  
>>>> magic of javascript. My eventual solution is probably going to break  
>>>> in some future release of Tapestry, and it may provoke some frowns,  
>>>> but I circumvented all of the Zone-specific javascript code in  
>>>> Tapestry be redefining Tapestry.initializeZones. The javascript below  
>>>> is for an ActionLink that works as an expand/collapse button for the  
>>>> Zone. The first time you expand the zone, it downloads the content  
>>>> from the server and stores it in memory. After that, it doesn't need  
>>>> to hit the server again. Note that this code doesn't support the inner  
>>>> "t-zone-update" <div> that Tapestry's built-in javascript supports.
>>>>
>>>>
>>>> MyObj = {
>>>>    linkZone: function (link, zone) {
>>>>      zone = $(zone);
>>>>      link = $(link);
>>>>      var expanded = false;
>>>>      var origHTML = zone.innerHTML;
>>>>      var fullHTML;
>>>>
>>>>      link.onclick = function(event) {
>>>>        if( expanded ) {
>>>>          zone.innerHTML = origHTML;
>>>>          link.innerHTML = "expand";
>>>>          expanded = false;
>>>>        } else {
>>>>          if( !zone.everPopulated ) {
>>>>            var successHandler = function(transport) {
>>>>              var response = transport.responseText;
>>>>              fullHTML = eval("(" + response + ")").content;
>>>>              zone.innerHTML = fullHTML;
>>>>            };
>>>>            var request = new Ajax.Request(link.href, { onSuccess :  
>>>> successHandler });
>>>>            zone.everPopulated = true;
>>>>          } else {
>>>>            zone.innerHTML = fullHTML;
>>>>          }
>>>>          link.innerHTML = "collapse"
>>>>          expanded = true;
>>>>        }
>>>>        return false;
>>>>      };
>>>>    }
>>>> };
>>>>
>>>> Tapestry.initializeZones = function(zoneSpecs, linkSpecs) {
>>>>    $A(linkSpecs).each(function (spec)
>>>>    {
>>>>        MyObj.linkZone(spec[0],spec[1]);
>>>>    });
>>>> };
>>>>
>>>>
>>>>
>>>> Hope that helps!
>>>> Travis
>>>>
>>>>
>>>>
>>>>
>>>>
>>>> On Feb 8, 2008, at 11:40 PM, Travis McLeskey wrote:
>>>>
>>>>    
>>>>        
>>>>> When an ActionLink and Zone appear together in a loop like this:
>>>>>
>>>>> <t:loop source="items" value="item">
>>>>>  <t:actionlink zone="myzone">go!</t:actionlink>
>>>>>  <t:zone t:id="myzone">in the zone?</t:zone>
>>>>>  <br />
>>>>> </t:loop>
>>>>>
>>>>> Clicking the "go!" link from any iteration only affects the Zone  
>>>>> from the first iteration. How do I connect each ActionLink to its  
>>>>> corresponding Zone? I tried injecting the Zone into the java class  
>>>>> and then using zone="${thezone.id}" in the actionlink, but then each  
>>>>> ActionLink was connected to the Zone from the *previous* iteration.
>>>>>
>>>>> Thanks!
>>>>> Travis
>>>>>
>>>>>      
>>>>>          
>>>> ---------------------------------------------------------------------
>>>> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
>>>> For additional commands, e-mail: users-help@tapestry.apache.org
>>>>
>>>>
>>>>
>>>>    
>>>>        
>>>  
>>>      
>>    
>
>  
Reply | Threaded
Open this post in threaded view
|

Re: T5: ActionLink/Zone components inside a loop (solved, better)

Axel Tetzlaff
Corin Lawson wrote
But it gave me another idea to free my code from getCurrentZoneId method. I am now significantly satisfied with the following solution. It makes for one very lean POJO.
But in cases like mine, where the zone comes after the links in the markup (which should be a quite common case for i.e. tabs) - the problem sadly remains, since the name of the zone can't be determined before rendering it