Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.click.control; import java.io.Serializable; import java.text.MessageFormat; import java.util.Comparator; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; import org.apache.click.Context; import org.apache.click.util.ClickUtils; import org.apache.click.util.HtmlStringBuffer; import org.apache.click.util.PropertyUtils; import org.apache.commons.lang.math.NumberUtils; /** * Provides the Column table data <td> and table header <th> * renderer. * * <table class='htmlHeader' cellspacing='10'> * <tr><td> * * <table id="table" class="isi"> * <thead> * <tr> * <th>Id</th> * <th>Name</th> * <th>Category</th> * <th>Action</th></tr></thead> * <tbody> * <tr class="odd"> * <td>834501</td> * <td>Alison Smart</td> * <td>Residential Property</td> * <td><a href="#">View</a></td></tr> * <tr class="even"> * <td>238454</td> * <td>Angus Robins</td> * <td>Bonds</td> * <td><a href="#">View</a></td></tr> * <tr class="odd"> * <td>784191</td> * <td>Ann Melan</td> * <td>Residential Property</td> * <td><a href="#">View</a></td></tr></tbody></table> * * </td></tr></table> * * <p/> * * The Column object provide column definitions for the {@link Table} object. * * <h3>Column Options</h3> * * The Column class supports a number of rendering options which include: * * <ul> * <li>{@link #autolink} - the option to automatically render href links * for email and URL column values</li> * <li>{@link #attributes} - the CSS style attributes for the table data cell</li> * <li>{@link #dataClass} - the CSS class for the table data cell</li> * <li>{@link #dataStyles} - the CSS styles for the table data cell</li> * <li>{@link #decorator} - the custom column value renderer</li> * <li>{@link #format} - the <tt>MessageFormat</tt> pattern rendering * the column value</li> * <li>{@link #headerClass} - the CSS class for the table header cell</li> * <li>{@link #headerStyles} - the CSS styles for the table header cell</li> * <li>{@link #headerTitle} - the table header cell value to render</li> * <li>{@link #sortable} - the table column sortable property</li> * <li>{@link #width} - the table cell width property</li> * </ul> * * <h4>Format Pattern</h4> * * The {@link #format} property which specifies {@link MessageFormat} pattern * a is very useful for formatting column values. For example to render * formatted number and date values you simply specify: * * <pre class="codeJava"> * Table table = <span class="kw">new</span> Table(<span class="st">"table"</span>); * table.setClass(<span class="st">"isi"</span>); * * Column idColumn = <span class="kw">new</span> Column(<span class="st">"purchaseId"</span>, <span class="st">"ID"</span>); * idColumn.setFormat(<span class="st">"{0,number,#,###}"</span>); * table.addColumn(idColumn); * * Column priceColumn = <span class="kw">new</span> Column(<span class="st">"purchasePrice"</span>, <span class="st">"Price"</span>); * priceColumn.setFormat(<span class="st">"{0,number,currency}"</span>); * priceColumn.setTextAlign(<span class="st">"right"</span>); * table.addColumn(priceColumn); * * Column dateColumn = <span class="kw">new</span> Column(<span class="st">"purchaseDate"</span>, <span class="st">"Date"</span>); * dateColumn.setFormat(<span class="st">"{0,date,dd MMM yyyy}"</span>); * table.addColumn(dateColumn); * * Column orderIdColumn = <span class="kw">new</span> Column(<span class="st">"order.id"</span>, <span class="st">"Order ID"</span>); * table.addColumn(orderIdColumn); </pre> * * <h4>Column Decorators</h4> * * The support custom column value rendering you can specify a {@link Decorator} * class on columns. The decorator <tt>render</tt> method is passed the table * row object and the page request Context. Using the table row you can access * all the column values enabling you to render a compound value composed of * multiple column values. For example: * * <pre class="codeJava"> * Column column = <span class="kw">new</span> Column(<span class="st">"email"</span>); * * column.setDecorator(<span class="kw">new</span> Decorator() { * <span class="kw">public</span> String render(Object row, Context context) { * Person person = (Person) row; * String email = person.getEmail(); * String fullName = person.getFullName(); * <span class="kw">return</span> <span class="st">"<a href='mailto:"</span> + email + <span class="st">"'>"</span> + fullName + <span class="st">"</a>"</span>; * } * }); * * table.addColumn(column); </pre> * The <tt>Context</tt> parameter of the decorator <tt>render()</tt> method enables you to * render controls to provide additional functionality. For example: * * <pre class="codeJava"> * <span class="kw">public class</span> CustomerList <span class="kw">extends</span> BorderedPage { * * <span class="kw">private</span> Table table = <span class="kw">new</span> Table(<span class="st">"table"</span>); * <span class="kw">private</span> ActionLink viewLink = <span class="kw">new</span> ActionLink(<span class="st">"view"</span>); * * <span class="kw">public</span> CustomerList() { * * viewLink.setListener(<span class="kw">this</span>, <span class="st">"onViewClick"</span>); * viewLink.setLabel(<span class="st">"View"</span>); * viewLink.setAttribute(<span class="st">"title"</span>, <span class="st">"View customer details"</span>); * table.addControl(viewLink); * * table.addColumn(<span class="kw">new</span> Column(<span class="st">"id"</span>)); * table.addColumn(<span class="kw">new</span> Column(<span class="st">"name"</span>)); * table.addColumn(<span class="kw">new</span> Column(<span class="st">"category"</span>)); * * Column column = <span class="kw">new</span> Column(<span class="st">"Action"</span>); * column.setDecorator(<span class="kw">new</span> Decorator() { * public String render(Object row, Context context) { * Customer customer = (Customer) row; * viewLink.setValue(<span class="st">""</span> + customer.getId()); * * return viewLink.toString(); * } * }); * table.addColumn(column); * * addControl(table); * } * * <span class="kw">public boolean</span> onViewClick() { * String path = getContext().getPagePath(Logout.class); * setRedirect(path + <span class="st">"?id="</span> + viewLink.getValue()); * <span class="kw">return true</span>; * } * } </pre> * * <h4>Internationalization</h4> * * Column header titles can be localized using the controls parent messages. * If the column header title value is null, the column will attempt to find a * localized message in the parent messages using the key: * <blockquote> * <tt>getName() + ".headerTitle"</tt> * </blockquote> * If not found then the message will be looked up in the * <tt>/click-control.properties</tt> file using the same key. * If a value still cannot be found then the Column name will be converted * into a header title using the method: {@link ClickUtils#toLabel(String)}. * <p/> * * @see Decorator * @see Table */ public class Column implements Serializable { private static final long serialVersionUID = 1L; // ----------------------------------------------------- Instance Variables /** The Column attributes Map. */ protected Map<String, String> attributes; /** * The automatically hyperlink column URL and email address values flag, * default value is <tt>false</tt>. */ protected boolean autolink; /** The column table data <td> CSS class attribute. */ protected String dataClass; /** The Map of column table data <td> CSS style attributes. */ protected Map<String, String> dataStyles; /** The column row decorator. */ protected Decorator decorator; /** The escape HTML characters flag. The default value is true. */ protected boolean escapeHtml = true; /** The column message format pattern. */ protected String format; /** The CSS class attribute of the column header. */ protected String headerClass; /** The Map of column table header <th> CSS style attributes. */ protected Map<String, String> headerStyles; /** The title of the column header. */ protected String headerTitle; /** * The maximum column length. If maxLength is greater than 0 and the column * data string length is greater than maxLength, the rendered value will be * truncated with an eclipse(...). * <p/> * Autolinked email or URL values will not be constrained. * <p/> * The default value is 0. */ protected int maxLength; /** * The optional MessageFormat used to render the column table cell value. */ protected MessageFormat messageFormat; /** The property name of the row object to render. */ protected String name; /** The column render id attribute status. The default value is false. */ protected Boolean renderId; /** The method cached for rendering column values. */ protected transient Map<Object, Object> methodCache; /** The column sortable status. The default value is false. */ protected Boolean sortable; /** The parent Table. */ protected Table table; /** * The property name used to populate the <td> "title" attribute. * The default value is null. */ protected String titleProperty; /** The column HTML <td> width attribute. */ protected String width; /** The column comparator object, which is used to sort column row values. */ @SuppressWarnings("unchecked") Comparator comparator; // ----------------------------------------------------------- Constructors /** * Create a table column with the given property name. The table header * title will be set as the capitalized property name. * * @param name the name of the property to render */ public Column(String name) { if (name == null) { throw new IllegalArgumentException("Null name parameter"); } this.name = name; } /** * Create a table column with the given property name and header title. * * @param name the name of the property to render * @param title the column header title to render */ public Column(String name, String title) { if (name == null) { throw new IllegalArgumentException("Null name parameter"); } if (title == null) { throw new IllegalArgumentException("Null title parameter"); } this.name = name; this.headerTitle = title; } /** * Create a Column with no name defined. * <p/> * <b>Please note</b> the control's name must be defined before it is valid. */ public Column() { } // ------------------------------------------------------ Public Properties /** * Return the Column HTML attribute with the given name, or null if the * attribute does not exist. * * @param name the name of column HTML attribute * @return the Column HTML attribute */ public String getAttribute(String name) { return getAttributes().get(name); } /** * Set the Column with the given HTML attribute name and value. These * attributes will be rendered as HTML attributes, for example: * <p/> * If there is an existing named attribute in the Column it will be replaced * with the new value. If the given attribute value is null, any existing * attribute will be removed. * * @param name the name of the column HTML attribute * @param value the value of the column HTML attribute * @throws IllegalArgumentException if attribute name is null */ public void setAttribute(String name, String value) { if (name == null) { throw new IllegalArgumentException("Null name parameter"); } if (value != null) { getAttributes().put(name, value); } else { getAttributes().remove(name); } } /** * Return the Column attributes Map. * * @return the column attributes Map. */ public Map<String, String> getAttributes() { if (attributes == null) { attributes = new HashMap<String, String>(); } return attributes; } /** * Return true if the Column has attributes or false otherwise. * * @return true if the column has attributes on false otherwise */ public boolean hasAttributes() { return (attributes != null && !getAttributes().isEmpty()); } /** * Return the flag to automatically render HTML hyperlinks for column URL * and email addresses values. * * @return automatically hyperlink column URL and email addresses flag */ public boolean getAutolink() { return autolink; } /** * Set the flag to automatically render HTML hyperlinks for column URL * and email addresses values. * * @param autolink the flag to automatically hyperlink column URL and * email addresses flag */ public void setAutolink(boolean autolink) { this.autolink = autolink; } /** * Return the column comparator object, which is used to sort column row * values. * * @return the column row data sorting comparator object. */ @SuppressWarnings("unchecked") public Comparator getComparator() { if (comparator == null) { comparator = new ColumnComparator(this); } return comparator; } /** * Set the column comparator object, which is used to sort column row * values. * * @param comparator the column row data sorting comparator object */ @SuppressWarnings("unchecked") public void setComparator(Comparator comparator) { this.comparator = comparator; } /** * Return the table data <td> CSS class. * * @return the table data CSS class */ public String getDataClass() { return dataClass; } /** * Set the table data <td> CSS class. * * @param dataClass the table data CSS class */ public void setDataClass(String dataClass) { this.dataClass = dataClass; } /** * Return the table data <td> CSS style. * * @param name the CSS style name * @return the table data CSS style for the given name */ public String getDataStyle(String name) { if (hasDataStyles()) { return getDataStyles().get(name); } else { return null; } } /** * Set the table data <td> CSS style name and value pair. * * @param name the CSS style name * @param value the CSS style value */ public void setDataStyle(String name, String value) { if (name == null) { throw new IllegalArgumentException("Null name parameter"); } if (value != null) { getDataStyles().put(name, value); } else { getDataStyles().remove(name); } } /** * Return true if table data <td> CSS styles are defined. * * @return true if table data <td> CSS styles are defined */ public boolean hasDataStyles() { return (dataStyles != null && !dataStyles.isEmpty()); } /** * Return the Map of table data <td> CSS styles. * * @return the Map of table data <td> CSS styles */ public Map<String, String> getDataStyles() { if (dataStyles == null) { dataStyles = new HashMap<String, String>(); } return dataStyles; } /** * Return the row column <td> decorator. * * @return the row column <td> decorator */ public Decorator getDecorator() { return decorator; } /** * Set the row column <td> decorator. * * @param decorator the row column <td> decorator */ public void setDecorator(Decorator decorator) { this.decorator = decorator; } /** * Return true if the HTML characters will be escaped when rendering the * column data. By default this method returns true. * * @return true if the HTML characters will be escaped when rendered */ public boolean getEscapeHtml() { return escapeHtml; } /** * Set the escape HTML characters when rendering column data flag. * * @param escape the flag to escape HTML characters */ public void setEscapeHtml(boolean escape) { this.escapeHtml = escape; } /** * Return the row column message format pattern. * * @return the message row column message format pattern */ public String getFormat() { return format; } /** * Set the row column message format pattern. For example: * * <pre class="javaClass"> * Column idColumn = <span class="kw">new</span> Column(<span class="st">"purchaseId"</span>, <span class="st">"ID"</span>); * idColumn.setFormat(<span class="st">"{0,number,#,###}"</span>); * * Column priceColumn = <span class="kw">new</span> Column(<span class="st">"purchasePrice"</span>, <span class="st">"Price"</span>); * priceColumn.setFormat(<span class="st">"{0,number,currency}"</span>); * * Column dateColumn = <span class="kw">new</span> Column(<span class="st">"purchaseDate"</span>, <span class="st">"Date"</span>); * dateColumn.setFormat(<span class="st">"{0,date,dd MMM yyyy}"</span>); </pre> * * <h4>MesssageFormat Patterns</h4> * * <table border='1' cellspacing='0' cellpadding='3'> * <tr bgcolor="#ccccff"> * <th id="ft">Format Type * <th id="fs">Format Style * <th id="sc">Subformat Created * <tr bgcolor="#ffffff"> * <td bgcolor="#eeeeff" headers="ft" rowspan=5><code>number</code> * <td headers="fs"><i>(none)</i> * <td headers="sc"><code>NumberFormat.getInstance(getLocale())</code> * <tr> * <td headers="fs"><code>integer</code> * <td headers="sc"><code>NumberFormat.getIntegerInstance(getLocale())</code> * <tr> * <td headers="fs"><code>currency</code> * <td headers="sc"><code>NumberFormat.getCurrencyInstance(getLocale())</code> * <tr> * <td headers="fs"><code>percent</code> * <td headers="sc"><code>NumberFormat.getPercentInstance(getLocale())</code> * <tr> * <td headers="fs"><i>SubformatPattern</i> * <td headers="sc"><code>new DecimalFormat(subformatPattern, new DecimalFormatSymbols(getLocale()))</code> * <tr> * <td bgcolor="#eeeeff" headers="ft" rowspan=6><code>date</code> * <td headers="fs"><i>(none)</i> * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code> * <tr> * <td headers="fs"><code>short</code> * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code> * <tr> * <td headers="fs"><code>medium</code> * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code> * <tr> * <td headers="fs"><code>long</code> * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code> * <tr> * <td headers="fs"><code>full</code> * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code> * <tr> * <td headers="fs"><i>SubformatPattern</i> * <td headers="sc"><code>new SimpleDateFormat(subformatPattern, getLocale())</code> * <tr> * <td bgcolor="#eeeeff" headers="ft" rowspan=6><code>time</code> * <td headers="fs"><i>(none)</i> * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code> * <tr> * <td headers="fs"><code>short</code> * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code> * <tr> * <td headers="fs"><code>medium</code> * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code> * <tr> * <td headers="fs"><code>long</code> * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code> * <tr> * <td headers="fs"><code>full</code> * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code> * <tr> * <td headers="fs"><i>SubformatPattern</i> * <td headers="sc"><code>new SimpleDateFormat(subformatPattern, getLocale())</code> * <tr> * <td bgcolor="#eeeeff" headers="ft"><code>choice</code> * <td headers="fs"><i>SubformatPattern</i> * <td headers="sc"><code>new ChoiceFormat(subformatPattern)</code> * </table> * * <h4>DecimalFormat Pattern Characters</h4> * * <table border='1' cellspacing='0' cellpadding='3'> * <tr bgcolor="#ccccff"> * <th align=left>Symbol * <th align=left>Location * <th align=left>Localized? * <th align=left>Meaning * <tr valign=top> * <td><code>0</code> * <td>Number * <td>Yes * <td>Digit * <tr valign=top bgcolor="#eeeeff"> * <td><code>#</code> * <td>Number * <td>Yes * <td>Digit, zero shows as absent * <tr valign=top> * <td><code>.</code> * <td>Number * <td>Yes * <td>Decimal separator or monetary decimal separator * <tr valign=top bgcolor="#eeeeff"> * <td><code>-</code> * <td>Number * <td>Yes * <td>Minus sign * <tr valign=top> * <td><code>,</code> * <td>Number * <td>Yes * <td>Grouping separator * <tr valign=top bgcolor="#eeeeff"> * <td><code>E</code> * <td>Number * <td>Yes * <td>Separates mantissa and exponent in scientific notation. * <em>Need not be quoted in prefix or suffix.</em> * <tr valign=top> * <td><code>;</code> * <td>Subpattern boundary * <td>Yes * <td>Separates positive and negative subpatterns * <tr valign=top bgcolor="#eeeeff"> * <td><code>%</code> * <td>Prefix or suffix * <td>Yes * <td>Multiply by 100 and show as percentage * <tr valign=top> * <td><code>\u2030</code> * <td>Prefix or suffix * <td>Yes * <td>Multiply by 1000 and show as per mille * <tr valign=top bgcolor="#eeeeff"> * <td><code>¤</code> (<code>\u00A4</code>) * <td>Prefix or suffix * <td>No * <td>Currency sign, replaced by currency symbol. If * doubled, replaced by international currency symbol. * If present in a pattern, the monetary decimal separator * is used instead of the decimal separator. * <tr valign=top> * <td><code>'</code> * <td>Prefix or suffix * <td>No * <td>Used to quote special characters in a prefix or suffix, * for example, <code>"'#'#"</code> formats 123 to * <code>"#123"</code>. To create a single quote * itself, use two in a row: <code>"# o''clock"</code>. * </table> * * <h4>SimpleDateFormat Pattern Characters</h4> * * <table border="1" cellspacing="0" cellpadding="3"> * <tr bgcolor="#ccccff"> * <th align=left>Letter * <th align=left>Date or Time Component * <th align=left>Presentation * <th align=left>Examples * <tr> * <td><code>G</code> * <td>Era designator * <td>Text * <td><code>AD</code> * <tr bgcolor="#eeeeff"> * <td><code>y</code> * <td>Year * <td>Year * <td><code>1996</code>; <code>96</code> * <tr> * <td><code>M</code> * <td>Month in year * <td>Month * <td><code>July</code>; <code>Jul</code>; <code>07</code> * <tr bgcolor="#eeeeff"> * <td><code>w</code> * <td>Week in year * <td>Number * <td><code>27</code> * <tr> * <td><code>W</code> * <td>Week in month * <td>Number * <td><code>2</code> * <tr bgcolor="#eeeeff"> * <td><code>D</code> * <td>Day in year * <td>Number * <td><code>189</code> * <tr> * <td><code>d</code> * <td>Day in month * <td>Number * <td><code>10</code> * <tr bgcolor="#eeeeff"> * <td><code>F</code> * <td>Day of week in month * <td>Number * <td><code>2</code> * <tr> * <td><code>E</code> * <td>Day in week * <td>Text * <td><code>Tuesday</code>; <code>Tue</code> * <tr bgcolor="#eeeeff"> * <td><code>a</code> * <td>Am/pm marker * <td>Text * <td><code>PM</code> * <tr> * <td><code>H</code> * <td>Hour in day (0-23) * <td>Number * <td><code>0</code> * <tr bgcolor="#eeeeff"> * <td><code>k</code> * <td>Hour in day (1-24) * <td>Number * <td><code>24</code> * <tr> * <td><code>K</code> * <td>Hour in am/pm (0-11) * <td>Number * <td><code>0</code> * <tr bgcolor="#eeeeff"> * <td><code>h</code> * <td>Hour in am/pm (1-12) * <td>Number * <td><code>12</code> * <tr> * <td><code>m</code> * <td>Minute in hour * <td>Number * <td><code>30</code> * <tr bgcolor="#eeeeff"> * <td><code>s</code> * <td>Second in minute * <td>Number * <td><code>55</code> * <tr> * <td><code>S</code> * <td>Millisecond * <td>Number * <td><code>978</code> * <tr bgcolor="#eeeeff"> * <td><code>z</code> * <td>Time zone * <td>General time zone * <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code> * <tr> * <td><code>Z</code> * <td>Time zone * <td>RFC 822 time zone * <td><code>-0800</code> * </table> * * @param pattern the message format pattern */ public void setFormat(String pattern) { this.format = pattern; } /** * The maximum column length. If maxLength is greater than 0 and the column * data string length is greater than maxLength, the rendered value will be * truncated with an eclipse(...). * * @return the maximum column length, or 0 if not defined */ public int getMaxLength() { return maxLength; } /** * Set the maximum column length. If maxLength is greater than 0 and the * column data string length is greater than maxLength, the rendered value * will be truncated with an eclipse(...). * * @param value the maximum column length */ public void setMaxLength(int value) { maxLength = value; } /** * Return the MessageFormat instance used to format the table cell value. * * @return the MessageFormat instance used to format the table cell value */ public MessageFormat getMessageFormat() { return messageFormat; } /** * Set the MessageFormat instance used to format the table cell value. * * @param messageFormat the MessageFormat used to format the table cell * value */ public void setMessageFormat(MessageFormat messageFormat) { this.messageFormat = messageFormat; } /** * Return the property name. * * @return the name of the property */ public String getName() { return name; } /** * Set the property name. * * @param name the property name to set */ public void setName(String name) { this.name = name; } /** * Return the table header <th> CSS class. * * @return the table header CSS class */ public String getHeaderClass() { return headerClass; } /** * Set the table header <th> CSS class. * * @param headerClass the table header CSS class */ public void setHeaderClass(String headerClass) { this.headerClass = headerClass; } /** * Return the table header <th> CSS style. * * @param name the CSS style name * @return the table header CSS style value for the given name */ public String getHeaderStyle(String name) { if (hasHeaderStyles()) { return getHeaderStyles().get(name); } else { return null; } } /** * Set the table header <th> CSS style name and value pair. * * @param name the CSS style name * @param value the CSS style value */ public void setHeaderStyle(String name, String value) { if (name == null) { throw new IllegalArgumentException("Null name parameter"); } if (value != null) { getHeaderStyles().put(name, value); } else { getHeaderStyles().remove(name); } } /** * Return true if table header <th> CSS styles are defined. * * @return true if table header <th> CSS styles are defined */ public boolean hasHeaderStyles() { return (headerStyles != null && !headerStyles.isEmpty()); } /** * Return the Map of table header <th> CSS styles. * * @return the Map of table header <th> CSS styles */ public Map<String, String> getHeaderStyles() { if (headerStyles == null) { headerStyles = new HashMap<String, String>(); } return headerStyles; } /** * Return the table header <th> title. * <p/> * If the header title value is null, this method will attempt to find a * localized message in the parent messages using the key: * <blockquote> * <tt>getName() + ".headerTitle"</tt> * </blockquote> * If not found then the message will be looked up in the * <tt>/click-control.properties</tt> file using the same key. * If a value still cannot be found then the Column name will be converted * into a header title using the method: {@link ClickUtils#toLabel(String)} * <p/> * * @return the table header title */ public String getHeaderTitle() { if (headerTitle == null) { headerTitle = getTable().getMessage(getName() + ".headerTitle"); } if (headerTitle == null) { headerTitle = ClickUtils.toLabel(getName()); } return headerTitle; } /** * Set the table header <th> title. * * @param title the table header title */ public void setHeaderTitle(String title) { headerTitle = title; } /** * Return the Table and Column id appended: "<tt>table-column</tt>" * <p/> * Use the field the "id" attribute value if defined, or the name otherwise. * * @return HTML element identifier attribute "id" value */ public String getId() { if (hasAttributes() && getAttributes().containsKey("id")) { return getAttribute("id"); } else { String tableId = (getTable() != null) ? getTable().getId() + "-" : ""; String id = tableId + getName(); if (id.indexOf('/') != -1) { id = id.replace('/', '_'); } if (id.indexOf(' ') != -1) { id = id.replace(' ', '_'); } return id; } } /** * Return the column sortable status. If the column sortable status is not * defined the value will be inherited from the * {@link Table#sortable} property. * * @return the column sortable status */ public boolean getSortable() { if (sortable == null) { if (getTable() != null) { return getTable().getSortable(); } else { return false; } } else { return sortable; } } /** * Set the column render id attribute status. * * @param value set the column render id attribute status */ public void setRenderId(boolean value) { renderId = value; } /** * Returns the column render id attribute status. If the column render id * status is not defined the value will be inherited from the * {@link Table#renderId} property. * * @return the column render id attribute status */ public boolean getRenderId() { if (renderId == null) { if (getTable() != null) { return getTable().getRenderId(); } else { return false; } } else { return renderId; } } /** * Set the column sortable status. * * @param value the column sortable status */ public void setSortable(boolean value) { sortable = value; } /** * Return the parent Table containing the Column. * * @return the parent Table containing the Column */ public Table getTable() { return table; } /** * Set the Column's the parent <tt>Table</tt>. * * @param table Column's parent <tt>Table</tt> */ public void setTable(Table table) { this.table = table; } /** * Set the column CSS "text-align" style for the header <th> and * data <td> elements. Valid values include: * <tt>[left, right, center]</tt> * * @param align the CSS "text-align" value: <tt>[left, right, center]</tt> */ public void setTextAlign(String align) { if (align != null && "middle".equalsIgnoreCase(align)) { String msg = "\"middle\" is not a valid CSS \"text-align\" " + "value, use \"center\" instead"; throw new IllegalArgumentException(msg); } if (!getSortable()) { setHeaderStyle("text-align", align); } setDataStyle("text-align", align); } /** * Return the property name used to populate the <td> "title" attribute. * * @return the property name used to populate the <td> "title" attribute */ public String getTitleProperty() { return titleProperty; } /** * Set the property name used to populate the <td> "title" attribute. * * @param property the property name used to populate the <td> "title" attribute */ public void setTitleProperty(String property) { titleProperty = property; } /** * Set the column CSS "vertical-align" style for the header <th> and * data <td> elements. Valid values include: * <tt>[baseline | sub | super | top | text-top | middle | bottom | * text-bottom | <percentage> | <length> | inherit]</tt> * * @param align the CSS "vertical-align" value */ public void setVerticalAlign(String align) { if (align != null && "center".equalsIgnoreCase(align)) { String msg = "\"center\" is not a valid CSS \"vertical-align\" " + "value, use \"middle\" instead"; throw new IllegalArgumentException(msg); } setHeaderStyle("vertical-align", align); setDataStyle("vertical-align", align); } /** * Return the column HTML <td> width attribute. * * @return the column HTML <td> width attribute */ public String getWidth() { return width; } /** * Set the column HTML <td> width attribute. * * @param value the column HTML <td> width attribute */ public void setWidth(String value) { width = value; } // --------------------------------------------------------- Public Methods /** * Render the column table data <td> element to the given buffer using * the passed row object. * * @param row the row object to render * @param buffer the string buffer to render to * @param context the request context * @param rowIndex the index of the current row within the parent table */ public void renderTableData(Object row, HtmlStringBuffer buffer, Context context, int rowIndex) { if (getMessageFormat() == null && getFormat() != null) { Locale locale = context.getLocale(); setMessageFormat(new MessageFormat(getFormat(), locale)); } buffer.elementStart("td"); if (getRenderId()) { String id = getId(); buffer.append(" id=\"").append(id).append("_").append(rowIndex).append("\""); } buffer.appendAttribute("class", getDataClass()); if (getTitleProperty() != null) { Object titleValue = getProperty(getTitleProperty(), row); buffer.appendAttributeEscaped("title", titleValue); } if (hasAttributes()) { buffer.appendAttributes(getAttributes()); } if (hasDataStyles()) { buffer.appendStyleAttributes(getDataStyles()); } buffer.appendAttribute("width", getWidth()); buffer.closeTag(); renderTableDataContent(row, buffer, context, rowIndex); buffer.elementEnd("td"); } /** * Render the column table header <tr> element to the given buffer. * * @param buffer the string buffer to render to * @param context the request context */ public void renderTableHeader(HtmlStringBuffer buffer, Context context) { buffer.elementStart("th"); boolean isSortable = getSortable() && !getTable().getRowList().isEmpty(); boolean sortedColumn = getName().equals(getTable().getSortedColumn()); boolean ascending = getTable().isSortedAscending(); if (isSortable) { String classValue = (getHeaderClass() != null) ? getHeaderClass() + " " : ""; if (sortedColumn) { classValue += (ascending) ? "ascending" : "descending"; } else { classValue += "sortable"; } buffer.appendAttribute("class", classValue); } else { buffer.appendAttribute("class", getHeaderClass()); } if (hasHeaderStyles()) { buffer.appendStyleAttributes(getHeaderStyles()); } if (hasAttributes()) { buffer.appendAttributes(getAttributes()); } buffer.closeTag(); if (isSortable) { ActionLink controlLink = getTable().getControlLink(); controlLink.setParameter(Table.COLUMN, getName()); controlLink.setParameter(Table.PAGE, String.valueOf(getTable().getPageNumber())); if (sortedColumn) { controlLink.setParameter(Table.ASCENDING, String.valueOf(ascending)); controlLink.setParameter(Table.SORT, "true"); } else { controlLink.setParameter(Table.ASCENDING, null); controlLink.setParameter(Table.SORT, null); } // Provide spacer for sorting icon controlLink.setLabel(getHeaderTitle() + "   "); controlLink.render(buffer); } else { if (getEscapeHtml()) { buffer.appendEscaped(getHeaderTitle()); } else { buffer.append(getHeaderTitle()); } } buffer.elementEnd("th"); } /** * Return the column name property value from the given row object. * <p/> * If the row object is a <tt>Map</tt> this method will attempt to return * the map value for the column name. The row map lookup will be performed * using the property name, if a value is not found the property name in * uppercase will be used, if a value is still not found the property name * in lowercase will be used. If a map value is still not found then this * method will return null. * <p/> * Object property values can also be specified using a path expression. * * @param row the row object to obtain the property from * @return the named row object property value * @throws RuntimeException if an error occurred obtaining the property */ public Object getProperty(Object row) { return getProperty(getName(), row); } /** * Return the column property value from the given row object and property name. * <p/> * If the row object is a <tt>Map</tt> this method will attempt to return * the map value for the column. The row map lookup will be performed using * the property name, if a value is not found the property name in uppercase * will be used, if a value is still not found the property name in lowercase * will be used. If a map value is still not found then this method will * return null. * <p/> * Object property values can also be specified using a path expression. * * @param name the name of the property * @param row the row object to obtain the property from * @return the named row object property value * @throws RuntimeException if an error occurred obtaining the property */ @SuppressWarnings("unchecked") public Object getProperty(String name, Object row) { if (row instanceof Map) { Map map = (Map) row; Object object = map.get(name); if (object != null) { return object; } String upperCaseName = name.toUpperCase(); object = map.get(upperCaseName); if (object != null) { return object; } String lowerCaseName = name.toLowerCase(); object = map.get(lowerCaseName); if (object != null) { return object; } return null; } else { if (methodCache == null) { methodCache = new HashMap<Object, Object>(); } return PropertyUtils.getValue(row, name, methodCache); } } // ------------------------------------------------------ Protected Methods /** * Render the content within the column table data <td> element. * * @param row the row object to render * @param buffer the string buffer to render to * @param context the request context * @param rowIndex the index of the current row within the parent table */ protected void renderTableDataContent(Object row, HtmlStringBuffer buffer, Context context, int rowIndex) { if (getDecorator() != null) { String value = getDecorator().render(row, context); if (value != null) { buffer.append(value); } } else { Object columnValue = getProperty(row); if (columnValue != null) { if (getAutolink() && renderLink(columnValue, buffer)) { // Has been rendered } else if (getMessageFormat() != null) { Object[] args = new Object[] { columnValue }; String value = getMessageFormat().format(args); if (getMaxLength() > 0) { value = ClickUtils.limitLength(value, getMaxLength()); } if (getEscapeHtml()) { buffer.appendEscaped(value); } else { buffer.append(value); } } else { String value = columnValue.toString(); if (getMaxLength() > 0) { value = ClickUtils.limitLength(value, getMaxLength()); } if (getEscapeHtml()) { buffer.appendEscaped(value); } else { buffer.append(value); } } } } } /** * Render the given table cell value to the buffer as a <tt>mailto:</tt> * or <tt>http:</tt> hyperlink, or as an ordinary string if the value is * determined not be linkable. * * @param value the table cell value to render * @param buffer the StringBuffer to render to * @return a rendered email or web hyperlink if applicable */ protected boolean renderLink(Object value, HtmlStringBuffer buffer) { if (value != null) { String valueStr = value.toString(); // If email if (valueStr.indexOf('@') != -1 && !valueStr.startsWith("@") && !valueStr.endsWith("@")) { buffer.append("<a href=\"mailto:"); buffer.append(valueStr); buffer.append("\">"); buffer.append(valueStr); buffer.append("</a>"); return true; } else if (valueStr.startsWith("http")) { int index = valueStr.indexOf("//"); if (index != -1) { index += 2; } else { index = 0; } buffer.append("<a href=\""); buffer.append(valueStr); buffer.append("\">"); buffer.append(valueStr.substring(index)); buffer.append("</a>"); return true; } else if (valueStr.startsWith("www")) { buffer.append("<a href=\"http://"); buffer.append(valueStr); buffer.append("\">"); buffer.append(valueStr); buffer.append("</a>"); return true; } } return false; } // ---------------------------------------------------------- Inner Classes /** * Provides a table Column comparator for sorting table rows. * * @see org.apache.click.control.Column * @see org.apache.click.control.Table */ @SuppressWarnings("unchecked") static class ColumnComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; /** The sort ascending flag. */ protected int ascendingSort; /** The column to sort on. */ protected final Column column; /** * Create a new Column based row comparator. * * @param column the column to sort on */ public ColumnComparator(Column column) { this.column = column; } /** * @see java.util.Comparator#compare(Object, Object) * * @param row1 the first row to compare * @param row2 the second row to compare * @return the comparison result */ public int compare(Object row1, Object row2) { this.ascendingSort = column.getTable().isSortedAscending() ? 1 : -1; Object value1 = column.getProperty(row1); Object value2 = column.getProperty(row2); if (value1 instanceof Comparable && value2 instanceof Comparable) { if (value1 instanceof String || value2 instanceof String) { return stringCompare(value1, value2) * ascendingSort; } else { return ((Comparable) value1).compareTo(value2) * ascendingSort; } } else if (value1 != null && value2 != null) { return value1.toString().compareToIgnoreCase(value2.toString()) * ascendingSort; } else if (value1 != null && value2 == null) { return +1 * ascendingSort; } else if (value1 == null && value2 != null) { return -1 * ascendingSort; } else { return 0; } } // ------------------------------------------------------ Protected Methods /** * Perform a comparison on the given string values. * * @param value1 the first value to compare * @param value2 the second value to compare * @return the string comparison result */ protected int stringCompare(Object value1, Object value2) { String string1 = value1.toString().trim(); String string2 = value2.toString().trim(); StringTokenizer st1 = new StringTokenizer(string1); StringTokenizer st2 = new StringTokenizer(string2); String token1 = null; String token2 = null; while (st1.hasMoreTokens()) { token1 = st1.nextToken(); if (st2.hasMoreTokens()) { token2 = st2.nextToken(); int comp = 0; if (useNumericSort(token1, token2)) { comp = numericCompare(token1, token2); } else { comp = token1.compareToIgnoreCase(token2); } if (comp != 0) { return comp; } } else { return -1; } } return 0; } /** * Return true if a numeric sort should be used. * * @param value1 the first value to test * @param value2 the second value to test * @return true if a numeric sort should be used */ protected boolean useNumericSort(String value1, String value2) { return NumberUtils.isDigits(value1) && NumberUtils.isDigits(value2); } /** * Perform a numeric compare on the given string values. * * @param string1 the first string value to compare * @param string2 the second string value to compare * @return the numeric comparison result */ protected int numericCompare(String string1, String string2) { if (string1.length() > 0 && string2.length() > 0) { Double double1 = Double.valueOf(string1); Double double2 = Double.valueOf(string2); return double1.compareTo(double2); } else if (string1.length() > 0) { return 1; } else if (string2.length() > 0) { return -1; } else { return 0; } } } }