/* * RHQ Management Platform * Copyright (C) 2009-2010 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.core.gui.table.renderer; import org.ajax4jsf.component.UIDataAdaptor; import org.ajax4jsf.model.ExtendedDataModel; import org.ajax4jsf.resource.InternetResource; import org.jetbrains.annotations.NotNull; import org.rhq.core.gui.table.component.RowSelectorComponent; import org.rhq.core.gui.util.FacesComponentUtility; import javax.faces.component.UIComponent; import javax.faces.component.UIData; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.convert.Converter; import javax.faces.model.DataModel; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * An HTML renderer for {@link RowSelectorComponent}s (i.e. rhq:rowSelector). * * @author Ian Springer */ public class RowSelectorRenderer extends AbstractRenderer { private static final String TABLE_SCRIPT = "/org/rhq/core/gui/table/renderer/js/table.js"; private InternetResource[] scripts; // Could this be made static? @Override @SuppressWarnings("unchecked") public void decode(FacesContext context, UIComponent component) { validateParameters(context, component); RowSelectorComponent rowSelector = (RowSelectorComponent) component; UIData data = getEnclosingData(rowSelector); // We store the List of selected data objects in a request context attribute and add to it as the data table's // rows are iterated during the updateModelValues phase. If the row is selected, we add the corresponding // data object to the List. List selectedDataObjects = getSelectedRowDataObjects(context, data); // Always set the submitted value, so that, even if no rows were selected, the setter on the managed bean will // still be passed an empty List, rather than null. rowSelector.setSubmittedValue(selectedDataObjects); Set<String> selectedRowKeys = getSelectedRowKeys(context, data, rowSelector); String rowKeyString = getRowKeyAsString(context, component, data); if (selectedRowKeys.contains(rowKeyString)) { // The current row is selected - add the row's data object to our List. Object rowData = data.getRowData(); selectedDataObjects.add(rowData); } } @Override public void encodeBegin(FacesContext context, UIComponent component) throws IOException { validateParameters(context, component); RowSelectorComponent rowSelector = (RowSelectorComponent) component; ResponseWriter writer = context.getResponseWriter(); writer.startElement("input", component); writeIdAttributeIfNecessary(context, writer, component); RowSelectorComponent.Mode selectionMode = rowSelector.getMode(); String type = (selectionMode == RowSelectorComponent.Mode.single) ? "radio" : "checkbox"; writer.writeAttribute("type", type, "type"); String clientId = component.getClientId(context); writer.writeAttribute("name", clientId, "clientId"); UIData data = getEnclosingData(rowSelector); Object rowKey = getRowKey(data); writer.writeAttribute("value", rowKey, null); // TODO: Write 'checked' attribute to allow checkbox to be selected by default? Probably overkill. String onclick = "updateButtons('" + clientId + "')"; String userSpecifiedOnclick = (String) rowSelector.getAttributes().get("onclick"); if (userSpecifiedOnclick != null) { onclick += "; " + userSpecifiedOnclick; } writer.writeAttribute("onclick", onclick, "onclick"); // TODO: Add support for all the other common HTML attributes. //RenderKitUtils.renderPassThruAttributes(writer, component, ATTRIBUTES); Boolean disabled = (Boolean) rowSelector.getAttributes().get("disabled"); if (disabled != null && disabled) { writer.writeAttribute("disabled", "disabled", "disabled"); } //RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component); writer.endElement("input"); } @Override public void encodeEnd(FacesContext context, UIComponent component) throws IOException { validateParameters(context, component); ResponseWriter writer = context.getResponseWriter(); writer.endElement("input"); } @NotNull private List getSelectedRowDataObjects(FacesContext context, UIData data) { Map<String, Object> requestMap = context.getExternalContext().getRequestMap(); initializeComponentId(context, data); String selectedRowDataObjectsRequestAttributeName = data.getId() + ":" + "selectedRowDataObjects"; List selectedDataObjects = (List) requestMap.get(selectedRowDataObjectsRequestAttributeName); if (selectedDataObjects == null) { selectedDataObjects = new ArrayList(); requestMap.put(selectedRowDataObjectsRequestAttributeName, selectedDataObjects); } return selectedDataObjects; } /* * Returns an immutable Set containing the String representations of the selected row keys. */ @NotNull private Set<String> getSelectedRowKeys(FacesContext context, UIData data, RowSelectorComponent rowSelector) { Map<String, Object> requestMap = context.getExternalContext().getRequestMap(); initializeComponentId(context, data); String selectedRowKeysRequestAttributeName = data.getId() + ":" + "selectedRowKeys"; Set<String> selectedRowKeys = (Set<String>) requestMap.get(selectedRowKeysRequestAttributeName); if (selectedRowKeys == null) { Map<String, String[]> requestParamMap = context.getExternalContext().getRequestParameterValuesMap(); String requestParamName = rowSelector.getClientId(context); String[] selectedRowKeyArray = requestParamMap.get(requestParamName); if (selectedRowKeyArray != null) { selectedRowKeys = new HashSet<String>(selectedRowKeyArray.length); Collections.addAll(selectedRowKeys, selectedRowKeyArray); selectedRowKeys = Collections.unmodifiableSet(selectedRowKeys); } else { selectedRowKeys = Collections.emptySet(); } } return selectedRowKeys; } private Object getRowKey(UIData data) { Object rowKey; DataModel dataModel = (DataModel) data.getAttributes().get("dataModel"); if (data instanceof UIDataAdaptor && dataModel instanceof ExtendedDataModel) { UIDataAdaptor dataAdaptor = (UIDataAdaptor) data; rowKey = dataAdaptor.getRowKey(); } else { Object rowData = data.getRowData(); rowKey = getPrimaryKey(rowData); } return rowKey; } private String getRowKeyAsString(FacesContext context, UIComponent component, UIData data) { String rowKeyString; Object rowKey = getRowKey(data); if (data instanceof UIDataAdaptor) { UIDataAdaptor dataAdaptor = (UIDataAdaptor) data; Converter rowKeyConverter = dataAdaptor.getRowKeyConverter(); rowKeyString = rowKeyConverter.getAsString(context, component, rowKey); } else { rowKeyString = String.valueOf(rowKey); } return rowKeyString; } /** * Return the specified row data object's primary key. Pretty much all RHQ domain objects use an Integer field named * 'id' as the primary key. * * TODO: Nevertheless, it would be nice to provide a way for the user to override this via an attribute on the * rowSelector component. * * @param object a row data object * @return the row data object's primary key - typically an Integer for RHQ domain objects */ private Object getPrimaryKey(Object object) { Method method; try { method = object.getClass().getMethod("getId"); } catch (NoSuchMethodException e) { throw new IllegalStateException(object.getClass() + " does not define a public getId() method."); } try { return method.invoke(object); } catch (Exception e) { throw new IllegalStateException("Failed to invoke getId() on " + object + ".", e); } } @NotNull private UIData getEnclosingData(UIComponent component) { UIData data = FacesComponentUtility.getAncestorOfType(component, UIData.class); if (data == null) { throw new IllegalStateException("Enclosing UIData component (i.e. h:dataTable or rich:*dataTable) not found for component " + component + "."); } return data; } /* (non-Javadoc) * @see org.ajax4jsf.renderkit.HeaderResourcesRendererBase#getScripts() */ protected InternetResource[] getScripts() { synchronized (this) { if (scripts == null) { scripts = new InternetResource[] { getResource(TABLE_SCRIPT) }; } } return scripts; } }