Details

    • Type: Improvement Improvement
    • Status: Resolved
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 1.5 M1
    • Fix Version/s: 1.5 M2
    • Component/s: None
    • Labels:
      None

      Description

      #1. Please add a chaining based HtmlStringBuffer utility class.
      I know that method chaining is pretty bad practice for many cases, and when available it's always totally misused (I revied myself allot of such code), but I think in the case of HtmlStringBuffer it would be an improvement and would make the rendering code (that is ugly indepenent of language due to HTML ) look a little bit nicer:
      Unchained:
      ----------------------------
      buffer.elementStart("button");
      buffer.appendAttribute("id", "theId");
      buffer.appendAttribute("type", "button");
      buffer.closeTag();
      buffer.append("Button Text");
      buffer.elementEnd("button);
      -----------------------------

      chained:
      -----------------------------
      buffer.elementStart("button")
      .appendAttribute("id", "theId")
      .appendAttribute("type", "button")
      .closeTag()
      .append("Button Text");
      buffer.elementEnd("button);
      -----------------------------

      or even (to simulate the HTML nesting):
      -----------------------------
      buffer.elementStart("button").appendAttribute("id", "theId").appendAttribute("type", "button") .closeTag()
      .append("Button Text");
      buffer.elementEnd("button);
      -----------------------------

      For more HTML elements and more nesting, the advantage is even more visible.

      #2. If it's possible, please shorten the syntax if possible (e.g. I think "appendAttribute()" is just too long).

        Activity

        Hide
        Demetrios Kyriakis added a comment -

        A eaxmple of a (poorly) hacked solution for #1 might look like below:
        ===============================================================
        package net.sf.click.util;

        import net.sf.click.util.HtmlStringBuffer;

        import java.util.Map;

        /**

        • Utility Class for Rendering HTML in Java. It "improves" (adapts) the default
        • <code>HtmlStringBuffer</code> utility with method chaining.
          */
          public class ChainedHtmlStringBuffer {
          HtmlStringBuffer parent;

        /**

        • Create a new HTML StringBuffer with the specified initial
        • capacity.
          *
        • @param length the initial capacity
          */
          public ChainedHtmlStringBuffer(int length) { parent = new HtmlStringBuffer(length); }

        /**

        • Create a new HTML StringBuffer with an initial capacity of 128
        • characters.
          */
          public ChainedHtmlStringBuffer() { parent = new HtmlStringBuffer(); }

        /**

        • Append the char value to the buffer.
          *
        • @param value the char value to append
        • @return <code>this</code> instance for chaining
          */
          public ChainedHtmlStringBuffer append(char value) { parent.append(value); return this; }

          /**
          * Append the integer value to the buffer.
          *
          * @param value the integer value to append
          * @return <code>this</code> instance for chaining
          */
          public ChainedHtmlStringBuffer append(int value) { parent.append(value); return this; }

        /**

        • Append the long value to the buffer.
          *
        • @param value the long value to append
        • @return <code>this</code> instance for chaining
          */
          public ChainedHtmlStringBuffer append(long value) { parent.append(value); return this; }

          /**
          * Append the raw object value of the given object to the buffer.
          *
          * @param value the object value to append
          * @return <code>this</code> instance for chaining
          */
          public ChainedHtmlStringBuffer append(Object value) { parent.append(value); return this; }

        /**

        • Append the raw string value of the given object to the buffer.
          *
        • @param value the string value to append
        • @return <code>this</code> instance for chaining
          */
          public ChainedHtmlStringBuffer append(String value) { parent.append(value); return this; }

        /**

        • Append the given value to the buffer and escape its HMTL value.
          *
        • @param value the object value to append
        • @return <code>this</code> instance for chaining
        • @throws IllegalArgumentException if the value is null
          */
          public ChainedHtmlStringBuffer appendEscaped(Object value) { parent.appendEscaped(value); return this; }

        /**

        • Append the given attribute name and value to the buffer, if the value
        • is not null.
        • <p/>
        • For example:
        • <pre class="javaCode">
        • appendAttribute(<span class="st">"class"</span>, <span class="st">"required"</span>) <span class="green">-></span> <span class="st">class="required"</span> </pre>
        • <p/>
        • The attribute value will be HMTL escaped. If the attribute name is a
        • JavaScript event handler the value will not be escaped.
          *
        • @param name the HTML attribute name
        • @param value the object value to append
        • @return <code>this</code> instance for chaining
        • @throws IllegalArgumentException if name is null
          */
          public ChainedHtmlStringBuffer appendAttribute(String name, Object value) { parent.appendAttribute(name, value); return this; }
          /**
          * Append the given HTML attribute name and value to the string buffer.
          * <p/>
          * For example:
          * <pre class="javaCode">
          * appendAttribute(<span class="st">"size"</span>, 10) <span class="green">-></span> <span class="st">size="10"</span> </pre>
          *
          * @param name the HTML attribute name
          * @param value the HTML attribute value
          * @return <code>this</code> instance for chaining
          * @throws IllegalArgumentException if name is null
          */
          public ChainedHtmlStringBuffer appendAttribute(String name, int value) { parent.appendAttribute(name, value); return this; }

        /**

        • Append the HTML "disabled" attribute to the string buffer.
        • <p/>
        • For example:
        • <pre class="javaCode">
        • appendAttributeDisabled() <span class="green">-></span> <span class="st">disabled="disabled"</span> </pre>
        • @return <code>this</code> instance for chaining
          */
          public ChainedHtmlStringBuffer appendAttributeDisabled() { parent.appendAttributeDisabled(); return this; }

        /**

        • Append the HTML "readonly" attribute to the string buffer.
        • <p/>
        • For example:
        • <pre class="javaCode">
        • appendAttributeReadonly() <span class="green">-></span> <span class="st">readonly="readonly"</span> </pre>
          *
        • @return <code>this</code> instance for chaining
          */
          public ChainedHtmlStringBuffer appendAttributeReadonly() { parent.appendAttributeReadonly(); return this; }

        /**

        • Append the given map of attribute names and values to the string buffer.
          *
        • @param attributes the map of attribute names and values
        • @return <code>this</code> instance for chaining
        • @throws IllegalArgumentException if attributes is null
          */
          public ChainedHtmlStringBuffer appendAttributes(Map attributes) { parent.appendAttributes(attributes); return this; }

        /**

        • Append the given map of CSS style name and value pairs as a style
        • attribute to the string buffer.
          *
        • @param attributes the map of CSS style names and values
        • @return <code>this</code> instance for chaining
        • @throws IllegalArgumentException if attributes is null
          */
          public ChainedHtmlStringBuffer appendStyleAttributes(Map attributes) { parent.appendStyleAttributes(attributes); return this; }

        /**

        • Append a HTML element end to the string buffer.
        • <p/>
        • For example:
        • <pre class="javaCode">
        • elementEnd(<span class="st">"textarea"</span>) <span class="green">-></span> <span class="st"></textarea></span> </pre>
          *
        • @param name the HTML element name to end
        • @return <code>this</code> instance for chaining
        • @throws IllegalArgumentException if name is null
          */
          public ChainedHtmlStringBuffer elementEnd(String name) { parent.elementEnd(name); return this; }

        /**

        • Append a HTML element end to the string buffer.
        • <p/>
        • For example:
        • <pre class="javaCode">
        • closeTag() <span class="green">-></span> <span class="st">></span> </pre>
          *
        • @return <code>this</code> instance for chaining
          */
          public ChainedHtmlStringBuffer closeTag() { parent.closeTag(); return this; }

        /**

        • Append a HTML element end to the string buffer.
        • <p/>
        • For example:
        • <pre class="javaCode">
        • elementEnd() <span class="green">-></span> <span class="st">/></span> </pre>
          *
        • @return <code>this</code> instance for chaining
          */
          public ChainedHtmlStringBuffer elementEnd() { parent.elementEnd(); return this; }

        /**

        • Append a HTML element start to the string buffer.
        • <p/>
        • For example:
        • <pre class="javaCode">
        • elementStart(<span class="st">"input"</span>) <span class="green">-></span> <span class="st"><input</span> </pre>
          *
        • @param name the HTML element name to start
        • @return <code>this</code> instance for chaining
          */
          public ChainedHtmlStringBuffer elementStart(String name) { parent.elementStart(name); return this; }

        /**

        • Return true if the given attribute name is a JavaScript attribute,
        • or false otherwise.
          *
        • @param name the HTML attribute name to test
        • @return true if the HTML attribute is a JavaScript attribute
          */
          public boolean isJavaScriptAttribute(String name) { return parent.isJavaScriptAttribute(name); //To change body of overridden methods use File | Settings | File Templates. }

        /**

        • Return the length of the string buffer.
          *
        • @return the length of the string buffer
          */
          public int length() { return parent.length(); //To change body of overridden methods use File | Settings | File Templates. }

        /**

        • @return a string representation of the string buffer
        • @see Object#toString()
          */
          public String toString() { return parent.toString(); //To change body of overridden methods use File | Settings | File Templates. }

          }

        ===============================================================

        Of course, another possibilty would be to improve the HtmlStringBuffer directly to support chaining.

        Show
        Demetrios Kyriakis added a comment - A eaxmple of a (poorly) hacked solution for #1 might look like below: =============================================================== package net.sf.click.util; import net.sf.click.util.HtmlStringBuffer; import java.util.Map; /** Utility Class for Rendering HTML in Java. It "improves" (adapts) the default <code>HtmlStringBuffer</code> utility with method chaining. */ public class ChainedHtmlStringBuffer { HtmlStringBuffer parent; /** Create a new HTML StringBuffer with the specified initial capacity. * @param length the initial capacity */ public ChainedHtmlStringBuffer(int length) { parent = new HtmlStringBuffer(length); } /** Create a new HTML StringBuffer with an initial capacity of 128 characters. */ public ChainedHtmlStringBuffer() { parent = new HtmlStringBuffer(); } /** Append the char value to the buffer. * @param value the char value to append @return <code>this</code> instance for chaining */ public ChainedHtmlStringBuffer append(char value) { parent.append(value); return this; } /** * Append the integer value to the buffer. * * @param value the integer value to append * @return <code>this</code> instance for chaining */ public ChainedHtmlStringBuffer append(int value) { parent.append(value); return this; } /** Append the long value to the buffer. * @param value the long value to append @return <code>this</code> instance for chaining */ public ChainedHtmlStringBuffer append(long value) { parent.append(value); return this; } /** * Append the raw object value of the given object to the buffer. * * @param value the object value to append * @return <code>this</code> instance for chaining */ public ChainedHtmlStringBuffer append(Object value) { parent.append(value); return this; } /** Append the raw string value of the given object to the buffer. * @param value the string value to append @return <code>this</code> instance for chaining */ public ChainedHtmlStringBuffer append(String value) { parent.append(value); return this; } /** Append the given value to the buffer and escape its HMTL value. * @param value the object value to append @return <code>this</code> instance for chaining @throws IllegalArgumentException if the value is null */ public ChainedHtmlStringBuffer appendEscaped(Object value) { parent.appendEscaped(value); return this; } /** Append the given attribute name and value to the buffer, if the value is not null. <p/> For example: <pre class="javaCode"> appendAttribute(<span class="st">"class"</span>, <span class="st">"required"</span>) <span class="green">-></span> <span class="st">class="required"</span> </pre> <p/> The attribute value will be HMTL escaped. If the attribute name is a JavaScript event handler the value will not be escaped. * @param name the HTML attribute name @param value the object value to append @return <code>this</code> instance for chaining @throws IllegalArgumentException if name is null */ public ChainedHtmlStringBuffer appendAttribute(String name, Object value) { parent.appendAttribute(name, value); return this; } /** * Append the given HTML attribute name and value to the string buffer. * <p/> * For example: * <pre class="javaCode"> * appendAttribute(<span class="st">"size"</span>, 10) <span class="green">-></span> <span class="st">size="10"</span> </pre> * * @param name the HTML attribute name * @param value the HTML attribute value * @return <code>this</code> instance for chaining * @throws IllegalArgumentException if name is null */ public ChainedHtmlStringBuffer appendAttribute(String name, int value) { parent.appendAttribute(name, value); return this; } /** Append the HTML "disabled" attribute to the string buffer. <p/> For example: <pre class="javaCode"> appendAttributeDisabled() <span class="green">-></span> <span class="st">disabled="disabled"</span> </pre> @return <code>this</code> instance for chaining */ public ChainedHtmlStringBuffer appendAttributeDisabled() { parent.appendAttributeDisabled(); return this; } /** Append the HTML "readonly" attribute to the string buffer. <p/> For example: <pre class="javaCode"> appendAttributeReadonly() <span class="green">-></span> <span class="st">readonly="readonly"</span> </pre> * @return <code>this</code> instance for chaining */ public ChainedHtmlStringBuffer appendAttributeReadonly() { parent.appendAttributeReadonly(); return this; } /** Append the given map of attribute names and values to the string buffer. * @param attributes the map of attribute names and values @return <code>this</code> instance for chaining @throws IllegalArgumentException if attributes is null */ public ChainedHtmlStringBuffer appendAttributes(Map attributes) { parent.appendAttributes(attributes); return this; } /** Append the given map of CSS style name and value pairs as a style attribute to the string buffer. * @param attributes the map of CSS style names and values @return <code>this</code> instance for chaining @throws IllegalArgumentException if attributes is null */ public ChainedHtmlStringBuffer appendStyleAttributes(Map attributes) { parent.appendStyleAttributes(attributes); return this; } /** Append a HTML element end to the string buffer. <p/> For example: <pre class="javaCode"> elementEnd(<span class="st">"textarea"</span>) <span class="green">-></span> <span class="st"></textarea></span> </pre> * @param name the HTML element name to end @return <code>this</code> instance for chaining @throws IllegalArgumentException if name is null */ public ChainedHtmlStringBuffer elementEnd(String name) { parent.elementEnd(name); return this; } /** Append a HTML element end to the string buffer. <p/> For example: <pre class="javaCode"> closeTag() <span class="green">-></span> <span class="st">></span> </pre> * @return <code>this</code> instance for chaining */ public ChainedHtmlStringBuffer closeTag() { parent.closeTag(); return this; } /** Append a HTML element end to the string buffer. <p/> For example: <pre class="javaCode"> elementEnd() <span class="green">-></span> <span class="st">/></span> </pre> * @return <code>this</code> instance for chaining */ public ChainedHtmlStringBuffer elementEnd() { parent.elementEnd(); return this; } /** Append a HTML element start to the string buffer. <p/> For example: <pre class="javaCode"> elementStart(<span class="st">"input"</span>) <span class="green">-></span> <span class="st"><input</span> </pre> * @param name the HTML element name to start @return <code>this</code> instance for chaining */ public ChainedHtmlStringBuffer elementStart(String name) { parent.elementStart(name); return this; } /** Return true if the given attribute name is a JavaScript attribute, or false otherwise. * @param name the HTML attribute name to test @return true if the HTML attribute is a JavaScript attribute */ public boolean isJavaScriptAttribute(String name) { return parent.isJavaScriptAttribute(name); //To change body of overridden methods use File | Settings | File Templates. } /** Return the length of the string buffer. * @return the length of the string buffer */ public int length() { return parent.length(); //To change body of overridden methods use File | Settings | File Templates. } /** @return a string representation of the string buffer @see Object#toString() */ public String toString() { return parent.toString(); //To change body of overridden methods use File | Settings | File Templates. } } =============================================================== Of course, another possibilty would be to improve the HtmlStringBuffer directly to support chaining.
        Hide
        Malcolm Edgar added a comment -

        Hi Demetrios,

        This is a pattern which we don't use, or encourage to use, in the Click framework because people will use it in other scenarios where the returning value might be null leading to a NullPointerException.

        This is an anti-pattern called the "train wreck". On production systems where you have only the exception stacktrace you have now way of knowing where it occured, when the methods are on the same line.

        regards Malcolm Edgar

        Show
        Malcolm Edgar added a comment - Hi Demetrios, This is a pattern which we don't use, or encourage to use, in the Click framework because people will use it in other scenarios where the returning value might be null leading to a NullPointerException. This is an anti-pattern called the "train wreck". On production systems where you have only the exception stacktrace you have now way of knowing where it occured, when the methods are on the same line. regards Malcolm Edgar
        Hide
        Demetrios Kyriakis added a comment -

        > This is a pattern which we don't use, or encourage to use, in the Click framework because people will use it in
        > other scenarios where the returning value might be null leading to a NullPointerException.
        > This is an anti-pattern called the "train wreck". On production systems where you have only the exception
        > stacktrace you have now way of knowing where it occured, when the methods are on the same line.
        Hi Malcolm,

        Thank you for your reply, but I'm aware of the above and I know that it's a very bad practice: I reviewed and fixed myself allot of such "chained" code and I know very well what an ugly job that is .

        In the above light, I think however that HtmlStringBuffer is the exception that confirms your rule, since most users will agree with me that the actual toString() (or newly render()) methods are very very hard to read (and modify/cusotmize).
        The use of template files for components (like in the case of RichTextArea) is a nice improvement, but I think it is practical only when designing totally new components: for performance reasons, once the component goes in production it must be converted to a pure "toString()" method.
        (I started to invertigate the possibility of a simple "Velcotiy Component Template" to "Java Click toString()" converter, and it seems simple enough - but I think such a thing belongs to an IDE in form of a "refactoring")

        I think HtmlStringBuffer should be chained, or have a similar approach to an older Apache Project called ECS:
        http://jakarta.apache.org/ecs/index.html
        Or please find another solution to make toString() code much more readable than it is now.

        I think this is very important since the power of every component oriented framework is the number and (re)usabilty of it's components (see the success of VB or Delphi - there are several thousands of components for each of them, and even if those products are "dead" projects based on them are alive and new components and projects are created)

        Thank you very much,

        Demetrios.

        Show
        Demetrios Kyriakis added a comment - > This is a pattern which we don't use, or encourage to use, in the Click framework because people will use it in > other scenarios where the returning value might be null leading to a NullPointerException. > This is an anti-pattern called the "train wreck". On production systems where you have only the exception > stacktrace you have now way of knowing where it occured, when the methods are on the same line. Hi Malcolm, Thank you for your reply, but I'm aware of the above and I know that it's a very bad practice: I reviewed and fixed myself allot of such "chained" code and I know very well what an ugly job that is . In the above light, I think however that HtmlStringBuffer is the exception that confirms your rule, since most users will agree with me that the actual toString() (or newly render()) methods are very very hard to read (and modify/cusotmize). The use of template files for components (like in the case of RichTextArea) is a nice improvement, but I think it is practical only when designing totally new components: for performance reasons, once the component goes in production it must be converted to a pure "toString()" method. (I started to invertigate the possibility of a simple "Velcotiy Component Template" to "Java Click toString()" converter, and it seems simple enough - but I think such a thing belongs to an IDE in form of a "refactoring") I think HtmlStringBuffer should be chained, or have a similar approach to an older Apache Project called ECS: http://jakarta.apache.org/ecs/index.html Or please find another solution to make toString() code much more readable than it is now. I think this is very important since the power of every component oriented framework is the number and (re)usabilty of it's components (see the success of VB or Delphi - there are several thousands of components for each of them, and even if those products are "dead" projects based on them are alive and new components and projects are created) Thank you very much, Demetrios.
        Hide
        Malcolm Edgar added a comment -

        Hi Demetrios,

        Have been looking at the StringBuffer implementation which this class is modelled after, so I think your request is valid.

        regards Malcolm Edgar

        Show
        Malcolm Edgar added a comment - Hi Demetrios, Have been looking at the StringBuffer implementation which this class is modelled after, so I think your request is valid. regards Malcolm Edgar

          People

          • Assignee:
            Malcolm Edgar
            Reporter:
            Demetrios Kyriakis
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development