/* | |
* 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.empire.jsf2.controls; | |
import java.util.Iterator; | |
import java.util.List; | |
import javax.el.ValueExpression; | |
import javax.faces.component.UIComponent; | |
import javax.faces.component.UISelectItem; | |
import javax.faces.component.html.HtmlSelectOneMenu; | |
import javax.faces.context.FacesContext; | |
import javax.faces.event.PhaseId; | |
import org.apache.empire.commons.ObjectUtils; | |
import org.apache.empire.commons.OptionEntry; | |
import org.apache.empire.commons.Options; | |
import org.apache.empire.commons.StringUtils; | |
import org.apache.empire.data.Column; | |
import org.apache.empire.exceptions.InvalidArgumentException; | |
import org.apache.empire.exceptions.UnexpectedReturnValueException; | |
import org.apache.empire.jsf2.app.TextResolver; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
public class SelectInputControl extends InputControl | |
{ | |
private static final Logger log = LoggerFactory.getLogger(SelectInputControl.class); | |
public static final String COLATTR_ABBR_OPTIONS = "ABBR_OPTIONS"; // Option list for abbreviations | |
public static final String VALUE_EXPRESSION_FLAG = "VALUE_EXPRESSION_FLAG"; | |
public static final String NAME = "select"; | |
private final Class<? extends HtmlSelectOneMenu> inputComponentClass; | |
public SelectInputControl(String name, Class<? extends HtmlSelectOneMenu> inputComponentClass) | |
{ | |
super(name); | |
this.inputComponentClass = inputComponentClass; | |
} | |
public SelectInputControl() | |
{ | |
this(SelectInputControl.NAME, HtmlSelectOneMenu.class); | |
} | |
/* for SelectTag (when no column is available) */ | |
public HtmlSelectOneMenu createMenuComponent(UIComponent parent) | |
{ | |
return InputControlManager.createComponent(FacesContext.getCurrentInstance(), this.inputComponentClass); | |
} | |
@Override | |
protected void createInputComponents(UIComponent parent, InputInfo ii, FacesContext context, List<UIComponent> compList) | |
{ | |
// check params | |
if (!compList.isEmpty()) | |
throw new InvalidArgumentException("compList", compList); | |
// create | |
HtmlSelectOneMenu input = InputControlManager.createComponent(context, this.inputComponentClass); | |
// setValueExpressionFlag | |
Object value = ii.getValue(false); | |
input.getAttributes().put(SelectInputControl.VALUE_EXPRESSION_FLAG, (value instanceof ValueExpression)); | |
// copy Attributes | |
copyAttributes(parent, ii, input); | |
// disabled | |
boolean disabled = ii.isDisabled(); | |
input.setDisabled(disabled); | |
// Options | |
initOptions(input, ii.getTextResolver(), ii); | |
// add | |
compList.add(input); | |
// style | |
addRemoveDisabledStyle(input, input.isDisabled()); | |
addRemoveInvalidStyle(input, ii.hasError()); | |
// Set Value | |
setInputValue(input, ii); | |
} | |
@Override | |
protected void updateInputState(List<UIComponent> compList, InputInfo ii, FacesContext context, PhaseId phaseId) | |
{ | |
UIComponent comp = compList.get(0); | |
if (!(comp instanceof HtmlSelectOneMenu)) | |
{ | |
throw new UnexpectedReturnValueException(comp.getClass().getName(), "parent.getChildren()"); | |
} | |
HtmlSelectOneMenu input = (HtmlSelectOneMenu)comp; | |
// required | |
addRemoveStyle(input, InputControl.STYLECLASS_REQUIRED, ii.isRequired()); | |
// disabled | |
boolean disabled = ii.isDisabled(); | |
input.setDisabled(disabled); | |
// check phase | |
if (phaseId!=PhaseId.APPLY_REQUEST_VALUES) | |
{ // Options (sync) | |
syncOptions(input, ii.getTextResolver(), ii); | |
} | |
if (phaseId==PhaseId.RENDER_RESPONSE) | |
{ // style | |
addRemoveDisabledStyle(input, input.isDisabled()); | |
addRemoveInvalidStyle(input, ii.hasError()); | |
// set value | |
setInputValue(input, ii); | |
} | |
} | |
protected boolean isEmptyEntryRequired(Options options, InputInfo ii, Object currentValue) | |
{ | |
if (options!=null && options.containsNull()) | |
{ // already has an empty option | |
return false; | |
} | |
// check required | |
if (ii.isRequired()==false) | |
{ // not required! | |
/* | |
* Old code: Why check this? | |
* | |
Column column = ii.getColumn(); | |
if (column==null || !(ii.isDisabled() && !column.isRequired())) | |
{ // add empty entry | |
return true; | |
} | |
*/ | |
return true; | |
} | |
// Check Value | |
return ObjectUtils.isEmpty(currentValue); | |
} | |
public void initOptions(HtmlSelectOneMenu input, TextResolver textResolver, InputInfo ii) | |
{ | |
// get the options | |
Options options = ii.getOptions(); | |
if (options==null) | |
{ // invalid options | |
if (ii.getColumn()!=null) | |
log.warn("No options given for column {}", ii.getColumn().getName()); | |
else | |
log.warn("No options given for select tag {}", input.getClientId()); | |
options = new Options(); | |
} | |
// current | |
Object currentValue = ii.getValue(true); | |
if (isEmptyEntryRequired(options, ii, currentValue)) | |
{ // Empty entry | |
addSelectItem(input, textResolver, new OptionEntry(null, getNullText(ii))); | |
} | |
if (options != null && options.size() > 0) | |
{ // Add options | |
for (OptionEntry oe : options) | |
{ // Option entries | |
if (oe.isActive() || ObjectUtils.compareEqual(oe.getValue(), currentValue)) | |
{ // add active or current item | |
addSelectItem(input, textResolver, oe); | |
} | |
else if (log.isDebugEnabled()) | |
{ // not active, ignore this one | |
log.debug("Select item {} is not active.", oe.getValue()); | |
} | |
} | |
} | |
} | |
public void syncOptions(HtmlSelectOneMenu input, TextResolver textResolver, InputInfo ii) | |
{ | |
// get the options | |
Options options = ii.getOptions(); | |
if (options==null) | |
{ // clear or not? | |
if (ii.getValue(false)!=null) | |
log.warn("No options have been set for column {}", ii.getColumn().getName()); | |
else | |
input.getChildren().clear(); | |
return; | |
} | |
Object currentValue = ii.getValue(true); | |
boolean hasEmpty = isEmptyEntryRequired(options, ii, currentValue); | |
// boolean isInsideUIData = ii.isInsideUIData(); | |
// Compare child-items with options | |
Iterator<OptionEntry> ioe = options.iterator(); | |
OptionEntry oe = (ioe.hasNext() ? ioe.next() : null); | |
List<UIComponent> childList = input.getChildren(); | |
Iterator<UIComponent> ico = childList.iterator(); | |
int lastIndex = 0; | |
boolean emptyPresent = false; | |
while (ico.hasNext()) | |
{ | |
lastIndex++; | |
UIComponent co = ico.next(); | |
if (!(co instanceof UISelectItem)) | |
continue; | |
UISelectItem si = (UISelectItem) co; | |
Object ov = si.getItemValue(); | |
if (ObjectUtils.isEmpty(ov) && hasEmpty) | |
{ emptyPresent = true; | |
continue; | |
} | |
// skip inactive | |
while (oe!=null && !oe.isActive()) | |
{ // check for current | |
if (ObjectUtils.compareEqual(oe.getValue(), currentValue)) | |
break; | |
// next oe | |
oe = (ioe.hasNext() ? ioe.next() : null); | |
} | |
if (oe == null) | |
{ // remove obsolete items | |
lastIndex--; | |
for (int index = childList.size() - 1; index >= lastIndex; index--) | |
childList.remove(index); | |
// done | |
return; | |
} | |
if (ObjectUtils.compareEqual(ov, oe.getValue())) | |
{ // next | |
oe = (ioe.hasNext() ? ioe.next() : null); | |
continue; | |
} | |
// Not equal - do a full reload | |
input.getChildren().clear(); | |
if (hasEmpty) { | |
// add empty entry | |
addSelectItem(input, textResolver, new OptionEntry("", getNullText(ii))); | |
} | |
for (OptionEntry opt : options) | |
{ // Option entries | |
if (opt.isActive() || ObjectUtils.compareEqual(opt.getValue(), currentValue)) | |
{ // add active or current item | |
addSelectItem(input, textResolver, opt); | |
} | |
} | |
// done | |
return; | |
} | |
// check empty entry | |
if (hasEmpty && !emptyPresent) | |
{ // add missing empty entry | |
addSelectItem(input, textResolver, new OptionEntry("", getNullText(ii)), 0); | |
} | |
// Are there any items left? | |
while (oe != null) | |
{ // add missing item | |
if (oe.isActive() || ObjectUtils.compareEqual(oe.getValue(), currentValue)) | |
{ // add item | |
addSelectItem(input, textResolver, oe); | |
} | |
oe = (ioe.hasNext() ? ioe.next() : null); | |
} | |
} | |
public void addSelectItem(UIComponent input, TextResolver textResolver, OptionEntry e, int pos) | |
{ | |
UISelectItem selectItem = new UISelectItem(); | |
// set value | |
Object value; | |
Object valueExpressionFlag = input.getAttributes().get(SelectInputControl.VALUE_EXPRESSION_FLAG); | |
if (ObjectUtils.getBoolean(valueExpressionFlag)) | |
{ // Use formatted value | |
value = formatInputValue(e.getValue()); | |
} | |
else | |
{ // Convert to String | |
value = e.getValueString(); | |
} | |
selectItem.setItemValue(value); | |
// set text | |
String text = e.getText(); | |
text = textResolver.resolveText(text); | |
selectItem.setItemLabel(text); | |
// add item | |
if (pos>=0) | |
input.getChildren().add(pos, selectItem); | |
else | |
input.getChildren().add(selectItem); | |
} | |
public void addSelectItem(UIComponent input, TextResolver textResolver, OptionEntry e) | |
{ | |
addSelectItem(input, textResolver, e, -1); | |
} | |
protected String getNullText(InputInfo ii) | |
{ | |
String nullText = getFormatString(ii, InputControl.FORMAT_NULL, InputControl.FORMAT_NULL_ATTRIBUTE); | |
return (nullText != null) ? ii.getText(nullText) : ""; | |
} | |
@Override | |
protected String formatValue(Object value, ValueInfo vi) | |
{ | |
// Lookup and Print value | |
if (vi.getOptions() == null) | |
{ | |
SelectInputControl.log.warn("Select field {} has no Option list attached!", vi.getColumn().getName()); | |
return super.formatValue(value, vi); | |
} | |
// Check for Abbreviation | |
if (hasFormatOption(vi, "short")) | |
{ | |
Column column = vi.getColumn(); | |
if (column != null) | |
{ // Check for Abbreviation option list | |
Object attrValue = column.getAttribute(SelectInputControl.COLATTR_ABBR_OPTIONS); | |
if (attrValue instanceof Options) | |
{ // Check for Options | |
String text = ((Options) attrValue).get(value); | |
if (StringUtils.isNotEmpty(text)) | |
return vi.getText(text); | |
// Error | |
if (value!=null) | |
SelectInputControl.log.error("The element '" + String.valueOf(value) + "' is not part of the supplied option list."); | |
} | |
} | |
} | |
return super.formatValue(value, vi); | |
} | |
@Override | |
protected Object formatInputValue(Object value, InputInfo ii) | |
{ | |
if ((value instanceof Number)) | |
{ // Check whether it's an Enum | |
Class<Enum<?>> enumType = ii.getColumn().getEnumType(); | |
if (enumType!=null) | |
{ // Convert ordinal to name | |
Enum<?> enumVal = ObjectUtils.getEnum(enumType, value); | |
value = enumVal.name(); | |
} | |
} | |
// the value | |
return formatInputValue(value); | |
} | |
protected Object formatInputValue(Object value) | |
{ | |
// the enum Value | |
if (value instanceof Enum<?>) | |
return ((Enum<?>) value).name(); | |
// the value | |
return value; | |
} | |
@Override | |
protected Object parseInputValue(String value, InputInfo ii) | |
{ | |
Class<Enum<?>> enumType = ii.getColumn().getEnumType(); | |
if (enumType!=null) | |
{ // convert to enum | |
return ObjectUtils.getEnumByName(enumType, value); | |
} | |
return value; | |
} | |
} |