/* * Copyright (c) 2016 OBiBa. All rights reserved. * * This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.obiba.wicket.markup.html.panel; import java.io.Serializable; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.authorization.Action; import org.apache.wicket.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.markup.repeater.RepeatingView; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.StringResourceModel; /** * A convenient class for generating tables of key/value pairs. * <p> * Data is presented in a two column table. The first column contains the keys and the second contains the values. Rows * are added by using one of the <code>addRow()</code> methods. * </p> */ @SuppressWarnings("UnusedDeclaration") public class KeyValueDataPanel extends Panel { private static final long serialVersionUID = 5705222737293170400L; RepeatingView view; ListView<?> myListView; int rowCounter = 0; private final WebMarkupContainer noDataRow = new WebMarkupContainer("noDataRow"); public KeyValueDataPanel(String id) { this(id, (Component) null); } /** * @param id * @param header use {@link #getHeaderId()} to put the right id to the component */ public KeyValueDataPanel(String id, Component header) { super(id); view = new RepeatingView("repeating"); add(view); add(new KeyValueDataPanelHeaderFragment(header)); noDataRow.add(new Label("noDataLabel", new StringResourceModel("datatable.no-records-found", this, null))); add(noDataRow); noDataRow.setVisible(false); } public KeyValueDataPanel(String id, IModel<?> header) { this(id, new Label(getHeaderId(), header)); } public static String getHeaderId() { return "tableLabel"; } public static String getRowKeyId() { return "rowKey"; } public static String getRowValueId() { return "rowValue"; } /** * Set/replace the current header label. * * @param header */ public void setHeader(IModel<?> header) { setHeader(new Label(getHeaderId(), header)); } /** * Set/replace the current header. * * @param header */ public void setHeader(Component header) { KeyValueDataPanelHeaderFragment newHeader = new KeyValueDataPanelHeaderFragment(header); addOrReplace(newHeader); } /** * When set to true, a row that spans both columns is made visible. Its content is a message that states that no data * was found to display. The resource key <code>datatable.no-records-found</code> is used to generate the message. * * @param show determines whether or not to show the row. */ public void showNoDataRow(boolean show) { noDataRow.setVisible(show); } /** * Add labels, with authorization filter. * * @param key * @param value * @param rowAuth */ public void addRow(IModel<?> key, IModel<?> value, RowAuthorization... rowAuth) { addRow(key, value, false, rowAuth); } /** * Add labels, with key indentation and authorization filter. * * @param key * @param value * @param indent * @param rowAuth */ public void addRow(IModel<?> key, IModel<?> value, boolean indent, RowAuthorization... rowAuth) { addRow(new Label(getRowKeyId(), key), createValueLabel(value), indent, rowAuth); } /** * Add components row, with authorization filter. * * @param key * @param value * @param rowAuth */ public void addRow(IModel<?> key, Component value, RowAuthorization... rowAuth) { addRow(key, value, false, rowAuth); } /** * Add components row, with key indentation and authorization filter. * * @param key * @param value * @param indent * @param rowAuth */ public void addRow(IModel<?> key, Component value, boolean indent, RowAuthorization... rowAuth) { addRow(new Label(getRowKeyId(), key), value, indent, rowAuth); } /** * Add components row, with authorization filter. * * @param key * @param value * @param rowAuth */ public void addRow(Component key, Component value, RowAuthorization... rowAuth) { addRow(key, value, false, rowAuth); } /** * Add components row, with authorization filter. * * @param key * @param value * @param rowAuth */ public void addRow(Component key, Component value, final boolean indent, RowAuthorization... rowAuth) { // If the value's is empty, generate an empty component with a spacing character. // This prevents some display glitches on the table. Component safeValue = value == null ? new EmptyCellFragment(getRowValueId()) : value; rowCounter++; WebMarkupContainer item = new WebMarkupContainer(view.newChildId()); view.add(item); item.add(key); key.add(new AttributeModifier("class", true, new AbstractReadOnlyModel() { private static final long serialVersionUID = 825034630638663648L; @Override public Object getObject() { return indent ? getKeyIndentCssClass() : getKeyCssClass(); } })); item.add(safeValue); if(rowAuth != null) { for(RowAuthorization aRowAuth : rowAuth) { MetaDataRoleAuthorizationStrategy.authorize(item, aRowAuth.getAction(), aRowAuth.getRoles()); } } safeValue.add(new AttributeModifier("class", true, new AbstractReadOnlyModel() { private static final long serialVersionUID = 8250197630638663648L; int rowIndex = rowCounter; @Override public Object getObject() { return rowIndex % 2 == 1 ? getValueOddCssClass() : getValueEvenCssClass(); } })); } protected String getValueOddCssClass() { return "keyValueTableValueOdd"; } protected String getValueEvenCssClass() { return "keyValueTableValueEven"; } protected String getKeyCssClass() { return "keyValueTableKey"; } protected String getKeyIndentCssClass() { return "keyValueTableKeyIndent"; } public static class RowAuthorization { private final Action action; private final String roles; public RowAuthorization(Action action, String roles) { this.action = action; this.roles = roles; } public Action getAction() { return action; } public String getRoles() { return roles; } } /** * Creates a label for the value cell of a row, if an <tt>IModel</tt> was provided instead of a <tt>Component</tt>. * * @param value the model to use for the value cell. * @return the label generated from the model, or null if the model contained a null value. */ private Label createValueLabel(IModel<?> value) { // Wrap the original model that returns a whitespace when the original model value is null. // This allows the table cell tag to always have a body (ie: never renders <td/>). // A display issue (GFLX-111) was caused by this. // The reason we don't return a null component is because the original model may return a value later in our // life-cycle. // TODO: find a way to fix the display issue instead of hacking the models (ie: use css) return new Label(getRowValueId(), new Model<Serializable>(value) { private static final long serialVersionUID = 7669446358458768567L; @Override public Serializable getObject() { IModel<?> o = (IModel<?>) super.getObject(); if(o.getObject() == null) return " "; return (Serializable) o.getObject(); } }); } private class KeyValueDataPanelHeaderFragment extends Fragment { private static final long serialVersionUID = -6785397649238353097L; private KeyValueDataPanelHeaderFragment(Component titleComponent) { super("header", "headerFragment", KeyValueDataPanel.this); if(titleComponent != null) { if(!titleComponent.getId().equals(getHeaderId())) { throw new IllegalArgumentException("KeyValueDataPanel header Component's id must be '" + getHeaderId() + "'"); } add(titleComponent); } else { setVisible(false); } } } private class EmptyCellFragment extends Fragment { private static final long serialVersionUID = -1448002319546204879L; private EmptyCellFragment(String id) { super(id, "emptyCellFragment", KeyValueDataPanel.this); } } }