/*
* OpenFaces - JSF Component Library 2.0
* Copyright (C) 2007-2012, TeamDev Ltd.
* licensing@openfaces.org
* Unless agreed in writing the contents of this file are subject to
* the GNU Lesser General Public License Version 2.1 (the "LGPL" License).
* 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.
* Please visit http://openfaces.org/licensing/ for more details.
*/
package org.openfaces.component.table;
import org.openfaces.component.table.impl.TableDataModel;
import org.openfaces.org.json.JSONArray;
import org.openfaces.renderkit.TableUtil;
import org.openfaces.renderkit.table.AbstractTableRenderer;
import org.openfaces.util.Components;
import org.openfaces.util.Rendering;
import org.openfaces.util.Resources;
import org.openfaces.util.ScriptBuilder;
import org.openfaces.util.ValueBindings;
import javax.el.ValueExpression;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Dmitry Pikhulya
*/
public class CheckboxColumn extends BaseColumn {
public static final String COMPONENT_TYPE = "org.openfaces.CheckboxColumn";
public static final String COMPONENT_FAMILY = "org.openfaces.CheckboxColumn";
private static final String DEFAULT_CLASS = "o_checkbox_column";
private static final String ROW_INDEXES_ONLY_FOR_TABLES_MESSAGE = "CheckboxColumn's rowIndexes property cannot be used for CheckboxColumn embedded into TreeTable. Row indexes are only available within DataTable.";
private static final String NODE_PATH_FOR_TREETABLE_MESSAGE = "CheckboxColumn's nodePaths property cannot be used for CheckboxColumn embedded into DataTable. Node paths are only available within TreeTable.";
private Boolean sortable;
private AbstractTableSelection selectedRows;
private Map<String, ValueExpression> deferredValueExpressions;
private Boolean disabled;
private Boolean visible;
private String onchange;
public CheckboxColumn() {
setRendererType("org.openfaces.CheckboxColumnRenderer");
getAttributes().put("defaultStyle", DEFAULT_CLASS);
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
@Override
public Object saveState(FacesContext context) {
Object superState = super.saveState(context);
return new Object[]{
superState,
saveAttachedState(context, selectedRows),
saveExpressionMap(context, deferredValueExpressions),
sortable,
disabled,
visible,
onchange
};
}
@Override
public void restoreState(FacesContext context, Object stateObj) {
Object[] state = (Object[]) stateObj;
int i = 0;
super.restoreState(context, state[i++]);
selectedRows = (AbstractTableSelection) restoreAttachedState(context, state[i++]);
deferredValueExpressions = restoreExpressionMap(context, state[i++]);
sortable = (Boolean) state[i++];
disabled = (Boolean) state[i++];
visible = (Boolean) state[i++];
onchange = (String) state[i++];
assignDataModel();
}
private Map<String, ValueExpression> restoreExpressionMap(FacesContext context, Object obj) {
if (obj == null)
return null;
Map<String, ValueExpression> result = new HashMap<String, ValueExpression>();
Set<Map.Entry<String, ValueExpression>> entries = ((Map<String, ValueExpression>) obj).entrySet();
for (Map.Entry<String, ValueExpression> entry : entries) {
result.put(entry.getKey(), (ValueExpression) restoreAttachedState(context, entry.getValue()));
}
return result;
}
private Object saveExpressionMap(FacesContext context, Map<String, ValueExpression> bindingMap) {
if (bindingMap == null)
return null;
Map<String, Object> result = new HashMap<String, Object>();
for (Map.Entry<String, ValueExpression> entry : bindingMap.entrySet()) {
result.put(entry.getKey(), saveAttachedState(context, entry.getValue()));
}
return result;
}
public boolean getSortable() {
return ValueBindings.get(this, "sortable", sortable, false);
}
public void setSortable(boolean sortable) {
this.sortable = sortable;
}
public void assignDataModel() {
AbstractTable table = getTable();
if (table == null)
throw new IllegalStateException("Check-box column must be inserted into a DataTable or TreeTable component");
initSelectedRowsIfNeeded(table);
selectedRows.setTable(table);
selectedRows.setModel((TableDataModel) table.getUiDataValue());
}
private void initSelectedRowsIfNeeded(AbstractTable table) {
if (selectedRows == null) {
if (table instanceof DataTable)
selectedRows = new MultipleRowSelection();
else if (table instanceof TreeTable)
selectedRows = new MultipleNodeSelection();
else
throw new IllegalStateException("Unknown AbstractTable implementation: " + table.getClass().getName());
if (deferredValueExpressions != null) {
Set<Map.Entry<String, ValueExpression>> bindings = deferredValueExpressions.entrySet();
for (Map.Entry<String, ValueExpression> bindingEntry : bindings) {
String name = bindingEntry.getKey();
ValueExpression expression = bindingEntry.getValue();
String adaptedPropertyName = adaptPropertyName(name, selectedRows);
selectedRows.setValueExpression(adaptedPropertyName, expression);
}
deferredValueExpressions = null;
}
}
}
public void encodeInitScript(FacesContext context, ScriptBuilder buf) throws IOException {
// it's important to render hidden field inside of the table rather than creating it dynamically elsewhere
// to ensure that it gets submitted with Ajax when only table is included into the Ajax "execute" phase.
ResponseWriter writer = context.getResponseWriter();
String valueFieldName = getClientId(context);
Rendering.renderHiddenField(writer, valueFieldName, "");
JSONArray checkedRowIndexes = new JSONArray();
if (selectedRows == null || selectedRows.getModel() == null)
assignDataModel();
/*getSelectedRowKeys can return List of Integer if it's rowSelection or JSONArray if it's cellSelection
* There is check on MultipleNodeSelection so getSelectedRowKeys guarantees to return List of Integer
* */
AbstractTable table = Components.getParentWithClass(this, AbstractTable.class);
@SuppressWarnings(value = "unchecked") List<Integer> rowIndexes = (List<Integer>) selectedRows.encodeSelectionIntoIndexes(table.getUnDisplayedSelectionAllowed());
for (Integer checkedRowIdx : rowIndexes) {
checkedRowIndexes.put(checkedRowIdx);
}
int colIndex = table.getRenderedColumns().indexOf(this);
buf.functionCall("O$.Table._initCheckboxColumn",
table,
colIndex,
valueFieldName,
checkedRowIndexes,
getOnchange()
).semicolon();
}
public void decodeSelectionFromIndexes(List<Integer> indexes) {
selectedRows.decodeSelectionFromIndexes(indexes);
}
public List<Integer> getRowIndexes() {
if (selectedRows instanceof MultipleRowSelection)
return ((MultipleRowSelection) selectedRows).getRowIndexes(false);
else if (selectedRows instanceof MultipleNodeSelection)
throw new UnsupportedOperationException(ROW_INDEXES_ONLY_FOR_TABLES_MESSAGE);
else
throw new IllegalStateException("selectedRows is " + (selectedRows != null ? selectedRows.getClass().getName() : "null"));
}
public void setRowIndexes(List<Integer> indexes) {
if (selectedRows instanceof MultipleRowSelection)
((MultipleRowSelection) selectedRows).setRowIndexes(indexes);
else if (selectedRows instanceof MultipleNodeSelection)
throw new UnsupportedOperationException(ROW_INDEXES_ONLY_FOR_TABLES_MESSAGE);
else
throw new IllegalStateException("selectedRows is " + (selectedRows != null ? selectedRows.getClass().getName() : "null"));
}
public List<Object> getRowDatas() {
if (selectedRows instanceof MultipleRowSelection)
return ((MultipleRowSelection) selectedRows).getRowDatas();
else if (selectedRows instanceof MultipleNodeSelection)
return ((MultipleNodeSelection) selectedRows).getNodeDatas();
else
throw new IllegalStateException("selectedRows is " + (selectedRows != null ? selectedRows.getClass().getName() : "null"));
}
public void setRowDatas(List<Object> rowDatas) {
if (selectedRows instanceof MultipleRowSelection)
((MultipleRowSelection) selectedRows).setRowDatas(rowDatas);
else if (selectedRows instanceof MultipleNodeSelection)
((MultipleNodeSelection) selectedRows).setNodeDatas(rowDatas);
else
throw new IllegalStateException("selectedRows is " + (selectedRows != null ? selectedRows.getClass().getName() : "null"));
}
public List<TreePath> getNodePaths() {
if (selectedRows instanceof MultipleRowSelection)
throw new UnsupportedOperationException(NODE_PATH_FOR_TREETABLE_MESSAGE);
else if (selectedRows instanceof MultipleNodeSelection)
return ((MultipleNodeSelection) selectedRows).getNodePaths();
else
throw new IllegalStateException("selectedRows is " + (selectedRows != null ? selectedRows.getClass().getName() : "null"));
}
public void setNodePaths(List<TreePath> nodePaths) {
if (selectedRows instanceof MultipleRowSelection)
throw new UnsupportedOperationException(NODE_PATH_FOR_TREETABLE_MESSAGE);
else if (selectedRows instanceof MultipleNodeSelection)
((MultipleNodeSelection) selectedRows).setNodePaths(nodePaths);
else
throw new IllegalStateException("selectedRows is " + (selectedRows != null ? selectedRows.getClass().getName() : "null"));
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
if (selectedRows == null || selectedRows.getModel() == null)
assignDataModel();
selectedRows.beforeEncode(); // todo: encodeBegin is invoked for each row, and selectedRows.beforeEncode() reads the selection binding, so this call should be moved to a place where it will be invoked just one per table rendering
super.encodeBegin(context);
}
@Override
public void processUpdates(FacesContext context) {
super.processUpdates(context);
selectedRows.processUpdates(context);
selectedRows.beforeInvokeApplication();
}
@Override
public void setValueExpression(String name, ValueExpression expression) {
if (MultipleRowSelection.ROW_INDEXES_PROPERTY.equals(name) ||
MultipleRowSelection.ROW_DATAS_PROPERTY.equals(name) ||
"nodePaths".equals(name)) {
if (selectedRows != null) {
String adaptedPropertyName = adaptPropertyName(name, selectedRows);
selectedRows.setValueExpression(adaptedPropertyName, expression);
} else {
if (deferredValueExpressions == null)
deferredValueExpressions = new HashMap<String, ValueExpression>();
deferredValueExpressions.put(name, expression);
}
} else
super.setValueExpression(name, expression);
}
private String adaptPropertyName(String name, AbstractTableSelection selection) {
if (selection instanceof MultipleRowSelection) {
if (name.equals(MultipleRowSelection.ROW_INDEXES_PROPERTY))
return name;
if (name.equals(MultipleRowSelection.ROW_DATAS_PROPERTY))
return name;
if (name.equals("nodePaths"))
throw new IllegalStateException(NODE_PATH_FOR_TREETABLE_MESSAGE);
throw new IllegalStateException("Unknown property: " + name);
} else if (selection instanceof MultipleNodeSelection) {
if (name.equals(MultipleRowSelection.ROW_INDEXES_PROPERTY))
throw new UnsupportedOperationException(ROW_INDEXES_ONLY_FOR_TABLES_MESSAGE);
if (name.equals(MultipleRowSelection.ROW_DATAS_PROPERTY))
return "nodeDatas";
if (name.equals("nodePaths"))
return name;
throw new IllegalStateException("Unknown property: " + name);
} else
throw new IllegalStateException("selection is " + (selection != null ? selection.getClass().getName() : "null"));
}
@Override
public ValueExpression getValueExpression(String name) {
if (MultipleRowSelection.ROW_INDEXES_PROPERTY.equals(name) ||
MultipleRowSelection.ROW_DATAS_PROPERTY.equals(name) ||
"nodePaths".equals(name)) {
if (selectedRows == null)
return deferredValueExpressions.get(name);
else
return selectedRows.getValueExpression(name);
} else
return super.getValueExpression(name);
}
public void rememberByKeys() {
selectedRows.rememberByKeys();
}
public void encodeOnAjaxNodeFolding(FacesContext context) throws IOException {
if (selectedRows instanceof MultipleNodeSelection) {
/*getSelectedRowKeys can return List of Integer if it's rowSelection or JSONArray if it's cellSelection
* There is check on MultipleNodeSelection so getSelectedRowKeys guarantees to return List of Integer
* */
@SuppressWarnings(value = "unchecked") List<Integer> selectedRowIndexes = (List<Integer>) selectedRows.encodeSelectionIntoIndexes();
AbstractTable table = getTable();
List<BaseColumn> renderedColumns = table.getRenderedColumns();
int columnIndex = renderedColumns.indexOf(this);
ScriptBuilder buf = new ScriptBuilder().functionCall("O$.Table._setCheckboxColValues",
table,
columnIndex,
selectedRowIndexes
).semicolon();
Rendering.renderInitScript(context, buf,
Resources.utilJsURL(context),
TableUtil.getTableUtilJsURL(context),
AbstractTableRenderer.getTableJsURL(context)
);
} else if (!(selectedRows instanceof MultipleRowSelection)) {
throw new IllegalStateException("mySelectedRows is " + (selectedRows != null ? selectedRows.getClass().getName() : "null"));
}
}
public List getSelectedRowKeys() {
if (selectedRows instanceof MultipleRowSelection)
return ((MultipleRowSelection) selectedRows).getSelectedRowKeys();
else if (selectedRows instanceof MultipleNodeSelection)
return ((MultipleNodeSelection) selectedRows).getSelectedNodeKeyPaths();
else
throw new IllegalStateException("selectedRows is " + (selectedRows != null ? selectedRows.getClass().getName() : "null"));
}
public boolean isDisabled() {
return ValueBindings.get(this, "disabled", disabled, false);
}
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
public boolean getVisible() {
return ValueBindings.get(this, "visible", visible, true);
}
public void setVisible(boolean visible) {
this.visible = visible;
}
@Override
protected boolean getDefaultMenuAllowed() {
return false;
}
public String getOnchange() {
return ValueBindings.get(this, "onchange", onchange);
}
public void setOnchange(String onchange) {
this.onchange = onchange;
}
}