Velocity
  1. Velocity
  2. VELOCITY-174

For consideration: #define()...#end directive to define a block of VTL as a reference

    Details

    • Type: Improvement Improvement
    • Status: Resolved
    • Priority: Minor Minor
    • Resolution: Fixed
    • Affects Version/s: 1.3.1
    • Fix Version/s: 1.6
    • Component/s: Engine
    • Labels:
      None
    • Environment:
      Operating System: All
      Platform: All

      Description

      • Proposal:

      #define( $block_ref )

        1. any VTL / text content here
          ...
          #end

      then you can use it like a reference:
      <html><body> $!block_ref </body></html> / #if( $block_ref ).. #end / and so on

      • Justification:

      1. Allows template designer to use a more efficient inside -> out approach
      (similar to Nathan's VelocityLayoutServlet)
      2. Keeps macro framework separate (the app developer can still disallow inline
      macros)
      3. #macros to return blocks of VTL cannot be used like silent references or in
      other directives (like #if)
      4. The multi-line #set proposal requires a quoted value. A #define() block can
      contain anything (without the need to escape quotes for example).
      5. #define() blocks are proxied (like #macros) and thus change according to the
      context after they are defined, something a normal $reference or #set can't do.
      7. Can improve template management by reducing number of unique template components
      8. Can improve template security/errors by reducing the need for bottom tier
      template makers to worry about generic layout code and visa-versa.
      9. Merges the strengths of #macros and $references = best of both worlds

      • Usage Example: Custom HTML forms:

      template 1. : document_type_1_custom_form_body.vm


      #define( $custom_form_body )

        1. unique form fields here

      #end
      #parse("main_form_layout.vm")


      template 2 : main_form_layout.vm


      #define( $custom_page_body )
      <form>

        1. common form fields here

      $!custom_form_body

      <input type="submit" value="submit">
      </form>

      #end
      #parse("main_page_layout.vm")


      template 2 : main_page_layout.vm


      <html><body>

        1. common page layout html here
          $!custom_page_body

      </body></html>


      If the #define() directive was not available a template designer would need to
      do this:

      template 1. : document_type_1_custom_form_body.vm


      #parse("main_page_layout_header.vm")
      #parse("main_form_layout_header.vm")

        1. unique form fields here
          #parse("main_form_layout_header.vm")
          #parse("main_page_layout_header.vm")

      Not only is the same output achieved with fewer #parse() directives (3 v 5), the
      unique templates are fewer and easier to manage. Template designers can make
      many document_type_X templates and need only to define the reference
      $custom_form_body and not worry about the overall page layout. Custom parts of
      the page are also only optionally required now; the main template can test for a
      reference and display a default block if it isn't defined.

      1. Extends.java
        6 kB
        bruce sherrod
      2. ASF.LICENSE.NOT.GRANTED--Define.java
        4 kB
        Andrew Tetlaw

        Issue Links

          Activity

          Hide
          Andrew Tetlaw added a comment -

          Created an attachment (id=6070)
          Example implementation I have used

          Show
          Andrew Tetlaw added a comment - Created an attachment (id=6070) Example implementation I have used
          Hide
          Nathan Bubna added a comment -

          For references, this was discussed at some length on the user list.

          http://marc.theaimsgroup.com/?t=105135638200001&r=1&w=2

          Show
          Nathan Bubna added a comment - For references, this was discussed at some length on the user list. http://marc.theaimsgroup.com/?t=105135638200001&r=1&w=2
          Hide
          Henning Schmiedehausen added a comment -

          Nice idea, definitely too late for 1.5. Scheduling for 1.6

          Show
          Henning Schmiedehausen added a comment - Nice idea, definitely too late for 1.5. Scheduling for 1.6
          Hide
          Andrew Tetlaw added a comment -

          Hey that's great!

          Show
          Andrew Tetlaw added a comment - Hey that's great!
          Hide
          MAKINO Hisanaga added a comment -

          Great!
          I have a favor to ask.
          That is:
          If there is same named definition,
          give preference to first definition over next one.

          Justification:
          1. Allows template designer to write default definition.
          2. Default definition can make templates more simple.

          Usage example:
          Think about a case as bellow.
          There's template A, B and C.
          All templates has same layout.
          A and B has same menu, and C has custom menu.
          With default definition, we can write template like this.

          base template: base_layout.vm


          <html><body>
          ...
          #define($menu)

            1. default menu here

          #end
          $menu
          ...

          </body></html>


          A.vm and B.vm


            1. define unique page body or something.
              ...
              #parse("base_layout.vm")

          C.vm


          #define($menu)

            1. cutom menu here

          #end

            1. define unique page body or something.
              ...
              #parse("base_layout.vm")
          Show
          MAKINO Hisanaga added a comment - Great! I have a favor to ask. That is: If there is same named definition, give preference to first definition over next one. Justification: 1. Allows template designer to write default definition. 2. Default definition can make templates more simple. Usage example: Think about a case as bellow. There's template A, B and C. All templates has same layout. A and B has same menu, and C has custom menu. With default definition, we can write template like this. base template: base_layout.vm <html><body> ... #define($menu) default menu here #end $menu ... </body></html> A.vm and B.vm define unique page body or something. ... #parse("base_layout.vm") C.vm #define($menu) cutom menu here #end define unique page body or something. ... #parse("base_layout.vm")
          Hide
          MAKINO Hisanaga added a comment -

          This is a sample code to "give preference to first definition over next one. " .
          Just change Define#render method as bellow.

          public boolean render( InternalContextAdapter context, Writer writer,
          Node node )
          throws IOException, MethodInvocationException,
          ResourceNotFoundException, ParseErrorException
          {
          /* put a BlockDefNode instance into the context,

          • using blockKey as key, for later inline rendering.
          • No checking is done. I just grab the second child node and assume
          • that it's the block! This might be dangerous.
            */
            if (!context.containsKey(blockKey)) { context.put( blockKey, new BlockDefNode( rsvc, context, writer,node.jjtGetChild(1) ) ); }

            return true;
            }

          Show
          MAKINO Hisanaga added a comment - This is a sample code to "give preference to first definition over next one. " . Just change Define#render method as bellow. public boolean render( InternalContextAdapter context, Writer writer, Node node ) throws IOException, MethodInvocationException, ResourceNotFoundException, ParseErrorException { /* put a BlockDefNode instance into the context, using blockKey as key, for later inline rendering. No checking is done. I just grab the second child node and assume that it's the block! This might be dangerous. */ if (!context.containsKey(blockKey)) { context.put( blockKey, new BlockDefNode( rsvc, context, writer,node.jjtGetChild(1) ) ); } return true; }
          Hide
          bruce sherrod added a comment -

          Here is a a related, helpful directive, something like django's "extends".
          Usage is:

          a.vm:
          header
          $body
          footer

          b.vm:
          #extends ("a.vm")
          #define ($body)
          body VTL code goes here
          #end
          #end

          Show
          bruce sherrod added a comment - Here is a a related, helpful directive, something like django's "extends". Usage is: a.vm: header $body footer b.vm: #extends ("a.vm") #define ($body) body VTL code goes here #end #end
          Hide
          bruce sherrod added a comment -

          Example implementation of #extends, which uses #define

          Show
          bruce sherrod added a comment - Example implementation of #extends, which uses #define
          Hide
          Will Glass-Husain added a comment -

          I'm not quite sure I get it. Is it like a #parse with a special reference defined? Essentially similar to:

          #define($body)
          body VTL code
          #end
          #parse('a.vm")

          Show
          Will Glass-Husain added a comment - I'm not quite sure I get it. Is it like a #parse with a special reference defined? Essentially similar to: #define($body) body VTL code #end #parse('a.vm")
          Hide
          Christopher Townson added a comment -

          I think this is an excellent suggestion - a really essential piece of functionality.

          Show
          Christopher Townson added a comment - I think this is an excellent suggestion - a really essential piece of functionality.
          Hide
          Christoph Reck added a comment -

          Looks to me like a #macro, declaring a $reference variable instead of a #thingy, and without parameters...
          But it is affected by the numerous macro properties (local-scope, etc.):

          #macro( body )
          body VTL code
          #end
          #parse('base_layout.vm")

          Or a multi-line set (does not look as nice - whitespace gobbling issue!):

          #set( $body = "body VTL code
          ")
          body VTL code
          #end
          #parse('base_layout.vm")

          So I do like the idea of introducing #define for the notion of a set with a body.

          We need to think over if the body should allow somehow to disable parsing, e.g.
          #define( $page )
          #literal
          $header
          $messages
          Some VTL that is not parsed
          $footer
          #end
          #end

          And then use it, replacing the context references:
          #eval( $page )

          Show
          Christoph Reck added a comment - Looks to me like a #macro, declaring a $reference variable instead of a #thingy, and without parameters... But it is affected by the numerous macro properties (local-scope, etc.): #macro( body ) body VTL code #end #parse('base_layout.vm") Or a multi-line set (does not look as nice - whitespace gobbling issue!): #set( $body = "body VTL code ") body VTL code #end #parse('base_layout.vm") So I do like the idea of introducing #define for the notion of a set with a body. We need to think over if the body should allow somehow to disable parsing, e.g. #define( $page ) #literal $header $messages Some VTL that is not parsed $footer #end #end And then use it, replacing the context references: #eval( $page )
          Hide
          bruce sherrod added a comment -

          Yes, Will – #extends is like #parse, but with two differences: (1) #extends appears first, rather than last, and (2) VTL in the body of the second template is not rendered. So, the "child" template only overrides the defined regions from the parent template. (via #set and #define).

          A more complex example:

          a.vm:
          #define ($header)
          default header
          #end
          #define ($body)
          default body
          #end
          #define ($footer)
          default footer
          #end
          $header
          $body
          $footer

          b.vm:
          #extends("a.vm")
          #define ($body)
          b body
          #end

          c.vm:
          #extends("a.vm")
          #define($header)
          c custom header
          #end
          #define ($body)
          c body
          #end

          This is conceptually similar to object inheritance with overridden virtual methods.
          It is based on django's template system: http://www.djangoproject.com/documentation/templates/#template-inheritance

          Show
          bruce sherrod added a comment - Yes, Will – #extends is like #parse, but with two differences: (1) #extends appears first, rather than last, and (2) VTL in the body of the second template is not rendered. So, the "child" template only overrides the defined regions from the parent template. (via #set and #define). A more complex example: a.vm: #define ($header) default header #end #define ($body) default body #end #define ($footer) default footer #end $header $body $footer b.vm: #extends("a.vm") #define ($body) b body #end c.vm: #extends("a.vm") #define($header) c custom header #end #define ($body) c body #end This is conceptually similar to object inheritance with overridden virtual methods. It is based on django's template system: http://www.djangoproject.com/documentation/templates/#template-inheritance
          Hide
          Nathan Bubna added a comment -

          The need for block macros could be a little bit diminished by the presence of #define, as macro bodies could then be passed as arguments.

          Show
          Nathan Bubna added a comment - The need for block macros could be a little bit diminished by the presence of #define, as macro bodies could then be passed as arguments.
          Hide
          Nathan Bubna added a comment -

          Just to record my opinion, i like #define, but i don't like #extends, at least not for the core. I might be ok with putting it in the distribution and supporting it, but i don't think it belongs in the default directives.properties. Better to let users who understand and want it turn it on themselves.

          For both of these, i would love to see some test cases. I'm happy to commit #define myself, but it'd be helpful if someone else could create the test case(s) for it. That would definitely speed it's adoption.

          Show
          Nathan Bubna added a comment - Just to record my opinion, i like #define, but i don't like #extends, at least not for the core. I might be ok with putting it in the distribution and supporting it, but i don't think it belongs in the default directives.properties. Better to let users who understand and want it turn it on themselves. For both of these, i would love to see some test cases. I'm happy to commit #define myself, but it'd be helpful if someone else could create the test case(s) for it. That would definitely speed it's adoption.
          Hide
          Jarkko Viinamäki added a comment -

          I think #define is at least very useful and it can be used to build blockmacro-type functionality. It might be necessary though to rethrow at least MethodInvocationException and RuntimeException in toString-method of the BlockDefNode.

          Show
          Jarkko Viinamäki added a comment - I think #define is at least very useful and it can be used to build blockmacro-type functionality. It might be necessary though to rethrow at least MethodInvocationException and RuntimeException in toString-method of the BlockDefNode.
          Hide
          Nathan Bubna added a comment -

          Ok, seems like most everyone liked #define, so i cleaned it up, added a testcase and put it in the core. Bruce, if you're still out there and interested in lobbying for #extends further, please feel free to do so. Probably best if you add a new JIRA issue for it, though.

          Show
          Nathan Bubna added a comment - Ok, seems like most everyone liked #define, so i cleaned it up, added a testcase and put it in the core. Bruce, if you're still out there and interested in lobbying for #extends further, please feel free to do so. Probably best if you add a new JIRA issue for it, though.
          Hide
          Nathan Bubna added a comment -

          Oh, i forgot to address Makino's concern about preferring the first definition...

          I think it is simplest and most intuitive to keep the order of definition as is, allowing later ones to override newer ones. You can always make the later, default definitions conditional: #if( !$foo )#define( $foo ) ... #end#end

          If that really isn't satisfactory, feel free to come up with a patch that makes the behavior configurable.

          Show
          Nathan Bubna added a comment - Oh, i forgot to address Makino's concern about preferring the first definition... I think it is simplest and most intuitive to keep the order of definition as is, allowing later ones to override newer ones. You can always make the later, default definitions conditional: #if( !$foo )#define( $foo ) ... #end#end If that really isn't satisfactory, feel free to come up with a patch that makes the behavior configurable.
          Hide
          bruce sherrod added a comment - - edited

          The #define macro appears not to work correctly in 1.6beta1. It now renders any time the variable is mentioned.

          So, for example:

          #define($foo)
          foo_contents
          #end
          #if ($foo)
          found foo
          #end

          Will render:
          foo_contents found foo

          when it should render:
          found foo

          p.s. I am happy to maintain Extends.java separately for my own project, if you guys don't choose to add it.

          Show
          bruce sherrod added a comment - - edited The #define macro appears not to work correctly in 1.6beta1. It now renders any time the variable is mentioned. So, for example: #define($foo) foo_contents #end #if ($foo) found foo #end Will render: foo_contents found foo when it should render: found foo p.s. I am happy to maintain Extends.java separately for my own project, if you guys don't choose to add it.
          Hide
          Nathan Bubna added a comment -

          That appears to work for me. I updated the test case to watch for this. Can you try this out with 1.6-beta2 and see if you are still experiencing the problem?

          Show
          Nathan Bubna added a comment - That appears to work for me. I updated the test case to watch for this. Can you try this out with 1.6-beta2 and see if you are still experiencing the problem?

            People

            • Assignee:
              Unassigned
              Reporter:
              Andrew Tetlaw
            • Votes:
              2 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development