This week's book giveaway is in the Kotlin forum.
We're giving away four copies of Kotlin in Action and have Dmitry Jemerov & Svetlana Isakova on-line!
See this thread for details.
Win a copy of Kotlin in Action this week in the Kotlin forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic

Taglibs, nested tags, access parent's current property  RSS feed

 
Chris Baron
Ranch Hand
Posts: 1061
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi,
i'am doing my first steps with custom taglibs.
I have a loop-tag that nests my testchild-tag.
With the latter one i try to output the current value of a property of it's parent.
I expected 1 2 3 4 5 as output. But it's 0 0 0 0 0 after the first and 5 5 5 5 5 in all following calls of the page.

How do i solve this?

The JSP


The loop class


The testchild class


TIA cb
[ October 18, 2005: Message edited by: Chris Baron ]
 
Mark Vedder
Ranch Hand
Posts: 624
IntelliJ IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Your logic is not correct for creating an iteration tag. In a nutshell this is how your logic breaks down:

  • Loop attributes are set, so Loop.setRepeat is called and count is set to 5.
  • Loop does not override BodyTagSupport.doStartTag(), so the method in BodyTagSupport is being called, which does nothing, and returns the default value of EVAL_BODY_BUFFERED
  • BodyTagSupport.setBodyContent is called since doStartTag() returned EVAL_BODY_BUFFERED (and it is not overiden in Loop)
  • BodyTagSupport.doInitBody() is called
  • (TestChild's attributes would be set now if any existed)
  • TestChild.doStartTag() is called
  • Loop.getCurrentCount is called (returning 0 since currentCount has not yet been set or iterated) and the value is writiten to its bodyContent
  • returns SKIP_BODY
  • (If Test Child had a body, and doStartTag returned EVAL_BODY_INCLUDE, the TestChild's body would be evaluated here)
  • Since TestChild does not override doEndTag, TagSupport.doEndTag is called and returns EVAL_PAGE
  • Loop.doAfterBody() is called (note that currentCount is still 0)
  • i is set to zero
  • your for loop is processed, each time it is outputting its bodyContent -- via your line out.print( body.getString() ); -- which TestChild wrote the String "0" to; so it is outputting a String zero, not the currentCount. If in TestChild you change the line: out.print( loop.getCurrentCount() ); to out.print("current count is " + loop.getCurrentCount() + " at " + System.currentTimeMillis());, you will see that the current time does not change. It is always the same since the BodyContent is only set once. More importantly, your ChildTag is only being processed once since your doAfterBody() never returns a value of EVAL_BODY_AGAIN. Your for loop does increment currentCount, but the getCurrentCount() method is never again called (since TestChild.doStartTag() is never called again since EVAL_BODY_AGAIN is never returned by Loop.doAfterBody()).
  • The for loop exists (currentCount is now 5, but again, getCurrentCount() is never called. It was only called once from ChildTag.doStartTag(), and was done so before currentCount was ever incremented.
  • Loop.doAfterBody() is exited
  • Since Loop does not override it, BodyTagSupport.doEndTag() is called and returns the default EVAL_PAGE


  • The order and logic of how these various methods get called can be confusing. Most JSP books that have a chapter on writing custom tags will flow chart or sequence diagram these for you. (The O'Reilly's book JavaServer Pages has a nice diagram, and Head First Servlets & JSP has some great information as well). And when first learning them, something that can be a big help is putting in some log4j debug output statements (or System.out if you don't mind the risk of having to delete them all later) with a bunch of good old fashioned "Entering such and such method" and "Got to xyz and value = blah" type of statements. They will help you trace what is happening.

    The correct way you want to implement an iteration tag is as follows:
  • Loop.doStartTag()
  • Check if the loop should be entered at all (for example, are there any elements in the thing you plan to iterator over)l if so return EVAL_BODY_INCLUDE;
  • else return SKIP_BODY; // in other words, there are no "elements" to evaluate
  • If you have any child tags, their method sequence is called at this point; i.e. Child.setAttributes(), Child.doStartTag(), Child.doAfterBoody() if applicable, Child.doEndTag()
  • In addition to any child tags, and other JSP/HTML content that is part of Loop's body is evaluated and "written" to the bodyContext
  • Loop.doAfterBody()
  • check if the another iteration of the loop should occur (you may first need to increment a counter), is so, make any variable changes, then return EVAL_BODY_AGAIN
  • else SKIP_BODY; // we have output all out values.


  • So for your Loop tag, you need to do the following:
  • Loop.doStartTag()
  • If count > 0 then currentCount++, return EVAL_BODY_INCLUDE; //you could also increment currentCount first, and make your logic test if ( currentCount <= count) then return EVAL_BODY_INCLUDE;
  • else return SKIP_BODY; // in other words, repeat attribute was zero, so we don't do any iterations
  • TestChild.doStartTag()
  • Output loop tag's currentCount value
  • Return SKIP_BODY (assuming you have no body content)
  • Loop.doAfterBody()
  • currentCount++
  • if currentCount <= count then return EVAL_BODY_AGAIN; //this will cause the Loop's body to get evaluated again, and thus TestChild.doStartTag() will be called again and output the next value
  • else SKIP_BODY; // we have output all out values.



  • It is very common (as in 95% of the time) in iteration tags that you have to repeat the same logic and code in the doStartTag() (for the first iteration) and the doAfterBody() (for subsequent iterations), and return the appropriate return value. This can be a maintenance hassle, especially if the logic gets complex. As such, I often do implementations that do something like this:


    and if you want to get real fancy, you could replace the if/else statements with a ternary operator (depending on whether you love them or hate them):


    Unfortunately, such a method is not always possible (or gets confusing) if you need to process things very differently the first time through the iteration as apposed to subsequent iterations.

    Anyhow, try implementing your tags following the above guideline. Let us know how it goes.
    [ October 18, 2005: Message edited by: Mark Vedder ]
     
    Mark Vedder
    Ranch Hand
    Posts: 624
    IntelliJ IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Oh, the reason why you were seeing 5 5 5 5 5 the second time you loaded the page is because some Web Containers may reuse an already created tag object. This would appear to be the what is happening to you. So the first time you use the tag, it's no argument constructor is called, and in addition any class attributes are set to their default for their type (0 in the case of currentCount since it's an int). The second time your tag runs, the object is being reused (and thus the constructor is not called, and class attributes are not initialized), and therefore currentCount is 5. It gets accessed by testChild when it is this value. Then your loop runs, and since your loop is setting currentCount to i+1, it then gets reset to 1,2,3,4 and 5 (but testChild never accesses it in any of these sates), and is therefore 5 at the end of the loop, and still 5 the next time the object is reused. If you changed the line currentCount = i+1; to currentCount++;, you would see your output increase by 5 (or whatever value repeat is) each time you refreshed the page. So you would see 0 0 0 0 0 then 5 5 5 5 5, then 10 10 10 10 10, etc


    So... an important point here is that since some containers may reuse a tag object, you need to always reinitialize your various class attributes in the doStartTag() method (since it is the first method called). Keep in mind however that any setAttribute() methods (such as setRepeat()) are called prior to the doStartTag() method, so you do not want to initialize those.

    Hope that helps.
     
    Chris Baron
    Ranch Hand
    Posts: 1061
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Thanks a lot for your very comprehensive anwer, Mark!
    This is what i've got after some puzzling. It works fine:



    I only overwrote doAfterBody(). The next thing i try, is to initialize/reset the member-variables in doStartTag(), for the case an tag doen't have an attribute.
    Thanks again,
    cb
     
    It is sorta covered in the JavaRanch Style Guide.
    • Post Reply Bookmark Topic Watch Topic
    • New Topic
    Boost this thread!