CCM NG: Current status of work on com.arsdigita.toolbox.ui.DataTable

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4453 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2016-11-22 18:36:15 +00:00
parent db8a7d08bc
commit 681ba87a68
11 changed files with 1198 additions and 14 deletions

View File

@ -48,7 +48,6 @@ import com.arsdigita.web.RedirectSignal;
import com.arsdigita.web.URL; import com.arsdigita.web.URL;
import org.libreccm.workflow.Workflow; import org.libreccm.workflow.Workflow;
import org.libreccm.workflow.WorkflowTemplate; import org.libreccm.workflow.WorkflowTemplate;
import org.apache.log4j.Logger;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
@ -71,8 +70,8 @@ public class ItemLanguages extends LayoutPanel {
/** /**
* Constructs a new <code>ItemLanguages</code>. * Constructs a new <code>ItemLanguages</code>.
* *
* @param selectionModel the {@link ItemSelectionModel} which will supply the current * @param selectionModel the {@link ItemSelectionModel} which will supply
* item * the current item
*/ */
public ItemLanguages(final ItemSelectionModel selectionModel) { public ItemLanguages(final ItemSelectionModel selectionModel) {
this.selectionModel = selectionModel; this.selectionModel = selectionModel;
@ -91,9 +90,11 @@ public class ItemLanguages extends LayoutPanel {
form.setRedirecting(true); form.setRedirecting(true);
languageWidget = new LanguageWidget(ContentItem.LANGUAGE) { languageWidget = new LanguageWidget(ContentItem.LANGUAGE) {
protected void setupOptions() { protected void setupOptions() {
// Don't do anything. // Don't do anything.
} }
}; };
try { try {
@ -127,10 +128,12 @@ public class ItemLanguages extends LayoutPanel {
final Pair pair = (Pair) iter.next(); final Pair pair = (Pair) iter.next();
final String langCode = (String) pair.getKey(); final String langCode = (String) pair.getKey();
final GlobalizedMessage langName final GlobalizedMessage langName
= (GlobalizedMessage) pair.getValue(); = (GlobalizedMessage) pair
.getValue();
optionGroup.addOption(new Option(langCode, new Label(langName))); optionGroup.addOption(new Option(langCode, new Label(langName)));
} }
} }
} }
/** /**
@ -142,7 +145,8 @@ public class ItemLanguages extends LayoutPanel {
throws FormProcessException { throws FormProcessException {
PageState state = e.getPageState(); PageState state = e.getPageState();
String lang = (String) languageWidget.getValue(state); String lang = (String) languageWidget.getValue(state);
ContentPage item = (ContentPage) selectionModel.getSelectedItem(state); ContentPage item = (ContentPage) selectionModel.getSelectedItem(
state);
ContentBundle bundle = item.getContentBundle(); ContentBundle bundle = item.getContentBundle();
String name = bundle.getName(); String name = bundle.getName();
@ -191,6 +195,7 @@ public class ItemLanguages extends LayoutPanel {
} }
} }
} }
} }
protected static final GlobalizedMessage gz(final String key) { protected static final GlobalizedMessage gz(final String key) {
@ -200,4 +205,5 @@ public class ItemLanguages extends LayoutPanel {
protected static final String lz(final String key) { protected static final String lz(final String key) {
return (String) gz(key).localize(); return (String) gz(key).localize();
} }
} }

View File

@ -18,7 +18,6 @@
*/ */
package com.arsdigita.cms.ui.item; package com.arsdigita.cms.ui.item;
import com.arsdigita.bebop.*;
import com.arsdigita.bebop.event.TableActionAdapter; import com.arsdigita.bebop.event.TableActionAdapter;
import com.arsdigita.bebop.event.TableActionEvent; import com.arsdigita.bebop.event.TableActionEvent;
import com.arsdigita.bebop.table.TableCellRenderer; import com.arsdigita.bebop.table.TableCellRenderer;

View File

@ -24,7 +24,7 @@ import java.util.NoSuchElementException;
/** /**
* Convenience extensions to {@link javax.swing.event.EventListenerList * Convenience extensions to {@link javax.swing.event.EventListenerList
* Swing's <code>EventListenerList</code>}. * Swing's <code>EventListenerList</code>}.
* @version $Id$ *
*/ */
public class EventListenerList extends javax.swing.event.EventListenerList { public class EventListenerList extends javax.swing.event.EventListenerList {

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package com.arsdigita.toolbox.ui;
import org.libreccm.cdi.utils.CdiUtil;
import javax.persistence.criteria.CriteriaQuery;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <T> Type of entity retrieved by the query.
*/
public abstract class AbstractDataQueryBuilder<T> implements DataQueryBuilder<T> {
/**
* Retrieves the {@link DataTableController} can creates a new
* {@code CriteriaQuery} using
* {@link DataTableController#createQuery(java.lang.Class)}.
*
* @param entityClass
*
* @return A new {@link CriteriaQuery} which can be further customised.
*/
protected CriteriaQuery<T> createBaseQuery(final Class<T> entityClass) {
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final DataTableController controller = cdiUtil.findBean(
DataTableController.class);
return controller.createQuery(entityClass);
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.toolbox.ui;
import com.arsdigita.util.Lockable;
import com.arsdigita.bebop.PageState;
import java.util.List;
import javax.persistence.criteria.CriteriaQuery;
/**
* This class is used by the {@link DataTable} class in order to construct a
* query during each request.
*
* In its original implementation this class used a {@code DataQuery}. Because
* this class is not longer available in JPA we had to change this class to use
* the JPA CriteriaQuery which is a rough equivalent in JPA. Also this class now
* uses generics.
*
* To create a {@link CriteriaQuery} the method
* {@link DataTableController#createQuery(java.lang.Class)} can be used. After
* the query is build
* {@link DataTableController#executeQuery(javax.persistence.criteria.CriteriaQuery)}
* can be used to execute the query. An instance of {@link DataTableController}
* can be obtained using the {@link org.libreccm.cdi.utils.CdiUtil} class.
*
* The abstract {@link AbstractDataQueryBuilder} can be used to avoid this
* boilerplate code. It provides the method
* {@link AbstractDataQueryBuilder#createBaseQuery(java.lang.Class)} which
* retrieves the {@link DataTableController} can creates a new
* {@link CriteriaQuery}.
*
* @param <T> the type of the entities the query should return.
*/
public interface DataQueryBuilder<T> extends Lockable {
/**
* Perform all necessary database operations and return a {@link List} for
* the {@link DataTable} to use.
*
* @param table the parent DataTable
* @param state the page state
*
* @return
*/
CriteriaQuery<T> makeDataQuery(DataTable<T> table, PageState state);
/**
* @return the name of the column in the query that serves as the primary
* key for the items
*/
String getKeyColumn();
}

View File

@ -0,0 +1,978 @@
/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.toolbox.ui;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.PaginationModelBuilder;
import com.arsdigita.bebop.Paginator;
import com.arsdigita.bebop.ParameterSingleSelectionModel;
import com.arsdigita.bebop.SingleSelectionModel;
import com.arsdigita.bebop.Table;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.ControlLink;
import com.arsdigita.bebop.Component;
import com.arsdigita.bebop.RequestLocal;
import com.arsdigita.bebop.event.EventListenerList;
import com.arsdigita.bebop.event.TableActionEvent;
import com.arsdigita.bebop.event.TableActionListener;
import com.arsdigita.bebop.table.DefaultTableCellRenderer;
import com.arsdigita.bebop.table.DefaultTableColumnModel;
import com.arsdigita.bebop.table.TableCellRenderer;
import com.arsdigita.bebop.table.TableColumn;
import com.arsdigita.bebop.table.TableColumnModel;
import com.arsdigita.bebop.table.TableModel;
import com.arsdigita.bebop.table.TableModelBuilder;
import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.util.LockableImpl;
import com.arsdigita.util.Assert;
import com.arsdigita.globalization.GlobalizedMessage;
import com.arsdigita.xml.Element;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.math.BigDecimal;
import org.apache.log4j.Logger;
import org.libreccm.cdi.utils.CdiUtil;
import java.util.ResourceBundle;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Root;
/**
* <h4>General</h4>
*
* Wraps any {@link List} in a sortable Bebop {@link Table}.
*
* The {@link List} is supplied by the {@link DataQueryBuilder} class, which the
* user must implement. The <code>DataQueryBuilder</code> may dynamically
* construct the query during each request, or return the same named query for
* each request; the <code>DataTable</code> does not care where the query comes
* from.
*
* This class may contain multiple {@link QueryListener}s. These listeners will
* be fired whenever the query is about to be performed, thus giving the user a
* chance to set additional filters on the query.
*
* Columns may be added to the <code>DataTable</code> by calling the
* {@link #addColumn} method. The user may choose to make the column sortable or
* non-sortable; sortable columns will appear as links on the Web page which,
* when clicked, will sort the table by the specified column. See the
* documentation on the various <code>addColumn</code> methods for more
* information.
*
*
* This class sets the XSL "class" attribute to "dataTable"
*
* <h4>Pagination</h4>
*
* <code>DataTable</code> also implements {@link PaginationModelBuilder}. This
* means that it could serve as the model builder for any {@link Paginator}
* component. Pagination of the query occurs after all the sorting and query
* events have finished. Consider a query which returns the rows "A B C D E F".
* If the paginator displays 3 rows per page, page 1 will contain "A B C" and
* page 2 will contain "D E F". If the user then clicks on the header in the
* <code>DataTable</code>, causing the query to be sorted in reverse order, page
* 1 will contain "F E D" and page 2 will contain "C B A". In order for
* pagination to work properly, the following pattern must be used:
*
* <blockquote><pre><code>
* DataTable table = new DataTable(...);
* Paginator paginator = new Paginator(table, ...);
* table.setPaginator(paginator);
* </code></pre></blockquote>
*
* The <code>setPaginator</code> call is required due to a design flaw in the
* <code>Paginator</code> component.
* <p>
*
* <h4>Globalization</h4>
*
* The <code>DataTable</code> will ordinarily interpret the labels of its column
* headers as plain text, and spit them out on the screen verbatim. However, if
* <code>setResouceBundle</code> is called, <code>DataTable</code> will instead
* interpret the column header labels as keys into the specified resource
* bundle, thus attempting to globalize the column headers at runtime.
* <p>
*
* @author Stanislav Freidin
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <T> Type of the entities in the table.
*/
public class DataTable<T> extends Table implements PaginationModelBuilder {
private DataQueryBuilder<T> dataQueryBuilder;
private SingleSelectionModel<String> orderModel;
private StringParameter dirParam;
private String resourceBundle;
private RequestLocal querySize;
private Paginator paginator;
public static final String ORDER = "o";
public static final String DIRECTION = "d";
public static final String ASCENDING = "asc";
public static final String DESCENDING = "desc";
private EventListenerList queryListeners;
/**
* Construct a new DataTable.
*
* @param dataQueryBuilder the {@link DataQueryBuilder} that will be used
* for this browser
* @param orderModel the {@link SingleSelectionModel} that will be
* used to determine the column to order by
* @param resourceBundle the name of the resource bundle that will be used
* to globalise the column labels. If null, column
* labels will be printed verbatim to the screen.
*/
public DataTable(final DataQueryBuilder<T> dataQueryBuilder,
final SingleSelectionModel<String> orderModel,
final String resourceBundle) {
super(new DataBuilderAdapter(), new DataTableColumnModel());
this.dataQueryBuilder = dataQueryBuilder;
this.resourceBundle = resourceBundle;
setOrderSelectionModel(orderModel);
addTableActionListener(new DataTableActionListener());
queryListeners = new EventListenerList();
dirParam = new StringParameter(DIRECTION);
dirParam.setDefaultValue(ASCENDING);
getHeader().setDefaultRenderer(new GlobalizedHeaderCellRenderer());
querySize = new RequestLocal();
paginator = null;
setClassAttr("dataTable");
}
/**
* Construct a new DataTable.
*
* @param dataQueryBuilder the {@link DataQueryBuilder} that will be used
* for this browser
* @param orderModel the {@link SingleSelectionModel} that will be
* used to determine the column to order by
*/
public DataTable(final DataQueryBuilder<T> dataQueryBuilder,
final SingleSelectionModel<T> orderModel) {
this(dataQueryBuilder, orderModel, null);
}
/**
* Construct a new DataTable
*
* @param dataQueryBuilder the {@link DataQueryBuilder} that will be used
* for this browser
*
*/
public DataTable(final DataQueryBuilder<T> dataQueryBuilder) {
this(dataQueryBuilder,
new ParameterSingleSelectionModel<T>(new StringParameter(ORDER)));
}
/**
* Register the ordering parameter
*/
@Override
public void register(final Page parent) {
super.register(parent);
parent.addComponentStateParam(this,
getOrderSelectionModel()
.getStateParameter());
parent.addComponentStateParam(this, dirParam);
}
/**
* Set the key of the default column which will be used to sort the entries
*
* @param attribute the default attribute to sort by
*/
public void setDefaultOrder(final String attribute) {
Assert.isUnlocked(this);
getOrderSelectionModel().getStateParameter()
.setDefaultValue(attribute);
}
/**
* Get the key of the default column which will be used to sort the entries
*
* @return the default attribute to sort by, or null if no default has been
* set
*/
public String getDefaultOrder() {
return (String) getOrderSelectionModel().getStateParameter()
.getDefaultValue();
}
/**
* Add a column to this table.
*
* @param label The user-readable label for the column NOTE: depending
* on setResourceBundle() it is treated as plain text for
* output or key into bundle resulting in globalized
* Labels!
* @param attribute The name of the attribute in the <code>DataQuery</code>
* which will be used as the value for this column.
* @param isSortable true if it is possible to sort using this column, false
* otherwise
* @param renderer a {@link TableCellRenderer} that will be used to format
* the attribute as a string.
*
* @return the newly added column
*/
public TableColumn addColumn(final String label,
final String attribute,
final boolean isSortable,
final TableCellRenderer renderer) {
return addColumn(label, attribute, isSortable, renderer, null);
}
/**
* Add a column to this table.
*
* @param label The user-readable label for the column NOTE:
* depending on setResourceBundle() it is treated as
* plain text for output or key into bundle resulting
* in globalised Labels!
* @param attribute The name of the attribute in the
* <code>DataQuery</code> which will be used as the
* value for this column.
* @param isSortable true if it is possible to sort using this column,
* false otherwise
* @param renderer a {@link TableCellRenderer} that will be used to
* format the attribute as a string.
* @param orderAttribute The name of the attribute which will be used as the
* column to order by. This key may be different from
* the <code>attribute</code> parameter.
*
* @return the newly added column
*/
public TableColumn addColumn(final String label,
final String attribute,
final boolean isSortable,
final TableCellRenderer renderer,
final String orderAttribute) {
DataTableColumnModel model = (DataTableColumnModel) getColumnModel();
TableColumn column = new SortableTableColumn(model.size(),
label,
attribute,
isSortable,
renderer
);
model.add(column, orderAttribute);
// Update the default sort order
if (isSortable && getDefaultOrder() == null) {
setDefaultOrder((orderAttribute == null) ? attribute
: orderAttribute);
}
return column;
}
/**
* Add a column to this table.
*
* @param label The user-readable label for the column NOTE: depending
* on setResourceBundle() it is treated as plain text for
* output or key into bundle resulting in globalized
* Labels!
* @param attribute The name of the attribute in the <code>DataQuery</code>
* which will be used as the value for this column.
* @param isSortable true if it is possible to sort using this column, false
* otherwise
*
* @return the newly added column
*/
public TableColumn addColumn(final String label,
final String attribute,
final boolean isSortable) {
return addColumn(label,
attribute,
isSortable,
new DefaultTableCellRenderer(false));
}
/**
* Add a column to this table.
*
* @param label The user-readable label for the column NOTE: depending
* on setResourceBundle() it is treated as plain text for
* output or key into bundle resulting in globalized
* Labels!
* @param attribute The name of the attribute in the <code>DataQuery</code>
* which will be used as the value for this column.
*
* @return the newly added column
*/
public TableColumn addColumn(final String label, final String attribute) {
return addColumn(label, attribute, false);
}
/**
* Add a column to this table. The value for the column will not be supplied
* by the query; instead, it is the user's responsibility to supply the
* value through a custom {@link TableModel} or render it directly in the
* {@link TableCellRenderer}. Typically, this method will be used to add
* {@link ControlLink}s to the table.
*
* @param label The user-readable label for the column NOTE: depending on
* setResourceBundle() it is treated as plain text for
* output or key into bundle resulting in globalized Labels!
* @param renderer The cell renderer for the given column
*
* @return the newly added column
*/
public TableColumn addColumn(final String label,
final TableCellRenderer renderer) {
final TableColumnModel columnModel = getColumnModel();
final TableColumn column = new TableColumn(columnModel.size(), label);
column.setCellRenderer(renderer);
column.setHeaderRenderer(new GlobalizedHeaderCellRenderer(false));
columnModel.add(column);
return column;
}
/**
*
* @return the {@link DataQueryBuilder} that creates a {@link DataQuery} for
* this table during each request
*/
public DataQueryBuilder<T> getDataQueryBuilder() {
return dataQueryBuilder;
}
/**
* @param builder the new {@link DataQueryBuilder} for this table
*/
public void setDataQueryBuilder(final DataQueryBuilder<T> builder) {
Assert.isUnlocked(this);
dataQueryBuilder = builder;
}
/**
* @return the {@link SingleSelectionModel} that will determine the order
*/
public SingleSelectionModel<String> getOrderSelectionModel() {
return orderModel;
}
/**
* Set the {@link SingleSelectionModel} that will determine the order for
* the items in the table.
*
* @param orderModel The new model
*/
public void setOrderSelectionModel(
final SingleSelectionModel<String> orderModel) {
Assert.isUnlocked(this);
this.orderModel = orderModel;
}
/**
* Add a {@link QueryListener} to this table. The listener will be fired
* whenever the query is about to be performed.
*
* @param listener the new query listener
*/
public void addQueryListener(final QueryListener listener) {
Assert.isUnlocked(this);
queryListeners.add(QueryListener.class, listener);
}
/**
* Remove a {@link QueryListener} from this table.
*
* @param listener the new query listener
*/
public void removeQueryListener(final QueryListener listener) {
Assert.isUnlocked(this);
queryListeners.remove(QueryListener.class, listener);
}
/**
* Fire the query event listeners to indicate that a query is about to be
* performed
*
* @param state The page state
* @param query The {@link DataQuery}
*/
protected void fireQueryPending(final PageState state,
final CriteriaQuery<T> query) {
final Iterator<QueryListener> iterator = queryListeners
.getListenerIterator(QueryListener.class);
QueryEvent<T> event = null;
while (iterator.hasNext()) {
if (event == null) {
event = new QueryEvent<>(this, state, query);
}
iterator.next().queryPending(event);
}
}
/**
* Set the column by which the table will be ordered
*
* @param state the page state
* @param attr the attribute by which the table will be sorted
*/
public void setOrder(final PageState state, final String attr) {
getOrderSelectionModel().setSelectedKey(state, attr);
}
/**
* @param state the page state
*
* @return the column by which the table will be ordered
*/
public String getOrder(final PageState state) {
return getOrderSelectionModel().getSelectedKey(state);
}
/**
* @param state the page state
*
* @return the order by which the currently selected column will be sorted;
* will be either ASCENDING or DESCENDING
*/
public String getOrderDirection(final PageState state) {
return (String) state.getValue(dirParam);
}
/**
* Set the sort direction
*
* @param state the page state
* @param dir the direction in which the current column should be sorted;
* either ASCENDING or DESCENDING
*/
public void setOrderDirection(final PageState state, final String dir) {
Assert.isTrue(ASCENDING.equals(dir) || DESCENDING.equals(dir));
state.setValue(dirParam, dir);
}
/**
* Toggle the sort direction between ascending and descending
*
* @param state the page state
*
* @return the new order direction; will be either ASCENDING or DESCENDING
*/
public String toggleOrderDirection(final PageState state) {
String dir = getOrderDirection(state);
dir = (ASCENDING.equals(dir)) ? DESCENDING : ASCENDING;
setOrderDirection(state, dir);
return dir;
}
/**
* Return the {@link DataQuery} that will be used during the current request
*
* @param state the page state for the current request
*
* @return the current <code>DataQuery</code>
*/
public CriteriaQuery<T> getDataQuery(final PageState state) {
return ((DataQueryTableModel) getTableModel(state)).getDataQuery();
}
/**
* Paginate the query according to the paginator component. This method will
* be automatically called by the {@link Paginator} component to which this
* <code>DataTable</code> has been added as the model builder.
*
* @param paginator the parent <code>Paginator</code>
* @param state the current page state
*
* @return the total number of rows in the query
*/
@Override
public int getTotalSize(final Paginator paginator, final PageState state) {
final CriteriaQuery<T> query = getDataQuery(state);
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final DataTableController controller = cdiUtil.findBean(
DataTableController.class);
return controller.executeQuery(query).size();
}
/**
* Return the paginator component used by this table, or null if the table
* is not paginated.
*
* @return The paginator.
*/
public final Paginator getPaginator() {
return paginator;
}
/**
* Set the paginator component used by this table, or null if the table
* should not be paginated.
*
* @param paginator The paginator to use.
*/
public final void setPaginator(final Paginator paginator) {
Assert.isUnlocked(this);
this.paginator = paginator;
}
/**
* Return the RequestLocal used for storing the query size during the
* request
*
* @return The query size.
*/
protected final RequestLocal getQuerySizeLocal() {
return querySize;
}
/**
* Lock this table
*/
@Override
public void lock() {
dataQueryBuilder.lock();
super.lock();
}
// Export the current order
@Override
public void generateExtraXMLAttributes(final PageState state,
final Element element) {
String key = getOrder(state);
if (key != null) {
element.addAttribute("order",
Integer
.toString(getColumnModel().getIndex(key)));
}
String dir = getOrderDirection(state);
if (dir != null) {
element.addAttribute("direction", dir);
}
}
/**
* Globalises the specified key.
*
* @param key The key of the message.
*
* @return The globalised message for the provided key.
*/
public GlobalizedMessage globalize(String key) {
return new GlobalizedMessage(key, resourceBundle);
}
/**
* Return the resource bundle for globalisation, or null if no bundle was
* specified
*
* @return The fully qualified name of the {@link ResourceBundle} used by
* this {@code DataTable}.
*/
public String getResourceBundle() {
return resourceBundle;
}
/**
* Set the resource bundle for globalisation, or null if no globalisation is
* needed.
*
* @param bundle The fully qualified name of the {@link ResourceBundle} to
* use.
*/
public void setResourceBundle(final String bundle) {
Assert.isUnlocked(this);
resourceBundle = bundle;
}
/**
* A {@link TableColumn} that could potentially be sorted
*/
private static class SortableTableColumn extends TableColumn {
private boolean sortable;
private SingleSelectionModel<String> orderModel;
/**
* Construct a new SortableTableColumn
*
* @param modelIndex the index of the column in the table model from
* which to retrieve values.
* @param value the value for the column header.
* @param key the key for the column header.
* @param isSortable whether the column is sortable or not
* @param renderer the renderer which will be used to render this
* column
*/
public SortableTableColumn(final int modelIndex,
final Object value,
final Object key,
final boolean isSortable,
final TableCellRenderer renderer
) {
super(modelIndex, value, key);
setSortable(isSortable);
setCellRenderer(renderer);
}
/**
* Determine whether this column is sortable
*
* @param isSortable if true, the column will be sortable
*/
public void setSortable(final boolean isSortable) {
Assert.isUnlocked(this);
sortable = isSortable;
setHeaderRenderer(new GlobalizedHeaderCellRenderer(isSortable));
}
/**
* @return the {@link SingleSelectionModel} which is responsible for
* maintaining the sort order
*/
public SingleSelectionModel<String> getOrderSelectionModel() {
return orderModel;
}
/**
* @return true if this column is sortable, false otherwise
*/
public boolean isSortable() {
return sortable;
}
}
/**
* The action listener that will sort the {@link DataQuery} for this table
*/
private static class DataTableActionListener implements TableActionListener {
@Override
public void cellSelected(final TableActionEvent event) {
}
@Override
public void headSelected(final TableActionEvent event) {
final PageState state = event.getPageState();
final DataTable<?> table = (DataTable<?>) event.getSource();
final int index = event.getColumn();
final SortableTableColumn column = (SortableTableColumn) table
.getColumnModel().get(index);
if (column != null) {
if (column.isSortable()) {
final DataTableColumnModel model
= (DataTableColumnModel) table
.getColumnModel();
final String oldOrder = table.getOrder(state);
String newOrder = model.getColumnKey(column);
if (newOrder == null) {
newOrder = (String) column.getHeaderKey();
}
if (oldOrder != null && oldOrder.equals(newOrder)) {
// Reverse direction
table.toggleOrderDirection(state);
} else {
table.setOrder(state, newOrder);
table.setOrderDirection(state, DataTable.ASCENDING);
}
}
}
}
}
/**
* Adapts a {@link DataQueryBuilder} into a {@link TableModelBuilder}. Wraps
* the query returned by the builder in a DataQueryTableModel.
*
* @see com.arsdigita.toolbox.ui.DataTable.DataQueryTableModel
*/
protected static class DataBuilderAdapter extends LockableImpl
implements TableModelBuilder {
/**
* Create a new <code>DataBuilderAdapter</code>
*/
public DataBuilderAdapter() {
super();
}
/**
* Obtain a {@link DataQuery} and apply query events to it. The query
* events may add additional filters to the query, among other things.
* Finally, retrieve the current sort column from the parent
* {@link DataTable} and apply it to the query
*
* @see com.arsdigita.toolbox.ui.DataTable.DataQueryTableModel
* @param table the parent {@link DataTable}
* @param state the current page state
*
* @return the final {@link DataQuery}, which is now ready to be wrapped
* in a DataQueryTableModel
*/
protected <E> CriteriaQuery<E> createQuery(final DataTable<E> table,
final PageState state) {
final CriteriaQuery<E> query = table.getDataQueryBuilder()
.makeDataQuery(table, state);
final Root<E> root = query.getR
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final DataTableController controller = cdiUtil.findBean(
DataTableController.class);
final CriteriaBuilder criteriaBuilder = controller
.getCriteriaBuilder();
String order = table.getOrder(state);
if (order != null) {
String dir = table.getOrderDirection(state);
if (dir != null) {
order += " " + dir;
}
query.orderBy(criteriaBuilder.asc())
query.addOrder(order);
}
table.fireQueryPending(state, query);
// Paginate the query if neccessary
if (table.getPaginator() != null) {
// Force the size to calculate before the range is set
if (table.getQuerySizeLocal().get(state) == null) {
table.getQuerySizeLocal().set(state, new BigDecimal(query
.size()));
}
// Paginate the query
query
.setRange(new Integer(table.getPaginator().getFirst(state)),
new Integer(table.getPaginator().getLast(state)
+ 1));
}
return query;
}
/**
* Construct a DataQueryTableModel by wrapping the query.
*
* @param table the parent {@link DataTable}
* @param s the current page state
*
* @see com.arsdigita.toolbox.ui.DataTable.DataQueryTableModel
* @return a DataQueryTableModel that will iterate through the query
*/
public TableModel makeModel(Table table, PageState s) {
DataTable t = (DataTable) table;
DataQuery d = createQuery(t, s);
if (d == null) {
return Table.EMPTY_MODEL;
}
return new DataQueryTableModel(t, d,
t.getDataQueryBuilder()
.getKeyColumn());
}
}
/**
* A TableModel which gets its data from a DataQuery. This TableModel is
* used in the {@link DataTable.DataBuilderAdapter} to iterate through the
* query returned by the {@link DataQueryBuilder} and generate rows for it
* on the screen.
*/
protected static class DataQueryTableModel implements TableModel {
private DataQuery m_data;
private DataTableColumnModel m_cols;
private String m_keyColumn;
/**
* Create a new <code>DataQueryTableModel</code>
*
* @param t the {@link DataTable} which needs this model
* @param data the {@link DataQuery to be wrapped}
* @param keyColumn the name of the column in the query which represents
* the primary key
*
* @pre data != null
* @pre keyColumn != null
* @pre t != null
* @pre t.getColumnModel() != null
*/
public DataQueryTableModel(DataTable t, DataQuery data, String keyColumn) {
m_data = data;
m_cols = (DataTableColumnModel) t.getColumnModel();
m_keyColumn = keyColumn;
}
public int getColumnCount() {
return m_cols.size();
}
public boolean nextRow() {
return m_data.next();
}
public Object getElementAt(int columnIndex) {
String key = (String) m_cols.get(columnIndex).getHeaderKey();
if (key != null) {
return m_data.get(key);
} else {
return null;
}
}
public Object getKeyAt(int columnIndex) {
String key = m_cols.getKeyAt(columnIndex);
if (key != null) {
return m_data.get(key);
} else {
return m_data.get(m_keyColumn);
}
}
/**
* Return the original DataQuery. The query's cursor will be "pointing"
* at the current row
*/
public DataQuery getDataQuery() {
return m_data;
}
}
/**
* Always renders the table header as a link. Thus, it becomes possible to
* sort up and down by clicking the table column over and over.<p>
* Also, globalizes the column labels if possible.
*/
protected static class GlobalizedHeaderCellRenderer
implements TableCellRenderer {
private boolean m_active;
public GlobalizedHeaderCellRenderer(boolean isActive) {
m_active = isActive;
}
public GlobalizedHeaderCellRenderer() {
this(true);
}
public Component getComponent(Table table, PageState state, Object value,
boolean isSelected, Object key,
int row, int column) {
DataTable t = (DataTable) table;
Label label;
if (value == null) {
label = new Label("&nbsp;", false);
} else {
String str = value.toString();
if (t.getResourceBundle() != null) {
label = new Label(t.globalize(str));
} else {
label = new Label(str);
}
}
if (m_active) {
return new ControlLink(label);
} else {
return label;
}
}
}
/**
* A special column model that maintains an alternate key for each column.
* The alternate key will be passed to the query in the
* <code>addOrder</code> method, thus sorting the query by the given column
* - making it possible to make the sort key differ from the attribute key
* for any given column.
* <p>
* Note that each column ALREADY has a unique key, which can be retrieved by
* calling <code>TableColumn.getHeaderKey()</code>. This key will be used to
* provide the value for the column.
*/
protected static class DataTableColumnModel extends DefaultTableColumnModel {
// The column keys are a property of the table and column
// combination so we store the values in the HashMap
private Map m_columnKeys = new HashMap();
public void add(TableColumn column, String columnKey) {
super.add(column);
setColumnKey(column, columnKey);
}
public void add(int columnIndex, TableColumn column, String columnKey) {
super.add(columnIndex, column);
setColumnKey(column, columnKey);
}
public String getColumnKey(TableColumn column) {
return (String) m_columnKeys.get(column);
}
public String getKeyAt(int columnIndex) {
return getColumnKey(get(columnIndex));
}
public void setColumnKey(TableColumn column, String columnKey) {
m_columnKeys.put(column, columnKey);
}
public void setColumnKey(int columnIndex, String columnKey) {
setColumnKey(get(columnIndex), columnKey);
}
@Override
public void remove(TableColumn column) {
super.remove(column);
m_columnKeys.remove(column);
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.toolbox.ui;
import com.arsdigita.bebop.Component;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.event.PageEvent;
import javax.persistence.criteria.CriteriaQuery;
/**
* This event is fired by the {@link DataTable} class
*
* @param <T> Type of the entities retrieved by the query.
* @see QueryListener
*/
public class QueryEvent<T> extends PageEvent {
private static final long serialVersionUID = -3616223193853983580L;
private CriteriaQuery<T> query;
public QueryEvent(final Component source,
final PageState state,
final CriteriaQuery<T> query) {
super(source, state);
this.query = query;
}
public CriteriaQuery<T> getDataQuery() {
return query;
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.toolbox.ui;
import java.util.EventListener;
/**
* This listener can be added to {@link DataTable} in order to
* set filters on the query, or perform other customisations.
*/
public interface QueryListener extends EventListener {
void queryPending(QueryEvent event);
}