/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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 software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.renderkit;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.el.ValueExpression;
import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.UIComponent;
import javax.faces.component.ValueHolder;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialResponseWriter;
import javax.faces.context.PartialViewContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import org.ajax4jsf.javascript.JSObject;
import org.ajax4jsf.javascript.JSReference;
import org.richfaces.application.ServiceTracker;
import org.richfaces.component.AbstractAttachQueue;
import org.richfaces.component.AbstractAutocomplete;
import org.richfaces.component.AbstractPlaceholder;
import org.richfaces.component.AutocompleteLayout;
import org.richfaces.component.AutocompleteMode;
import org.richfaces.component.MetaComponentResolver;
import org.richfaces.component.util.InputUtils;
import org.richfaces.context.ExtendedPartialViewContext;
import org.richfaces.el.GenericsIntrospectionService;
import org.richfaces.log.Logger;
import org.richfaces.log.RichfacesLogger;
/**
* @author Nick Belaevski
*/
@ResourceDependencies({ @ResourceDependency(library = "javax.faces", name = "jsf.js"),
@ResourceDependency(library = "org.richfaces", name = "jquery.js"),
@ResourceDependency(library = "org.richfaces", name = "richfaces.js"),
@ResourceDependency(library = "org.richfaces", name = "richfaces-queue.reslib"),
@ResourceDependency(library = "org.richfaces", name = "richfaces-base-component.js"),
@ResourceDependency(library = "org.richfaces", name = "jquery.position.js"), @ResourceDependency(library = "org.richfaces", name = "richfaces-event.js"),
@ResourceDependency(library = "org.richfaces", name = "richfaces-utils.js"), @ResourceDependency(library = "org.richfaces", name = "richfaces-selection.js"),
@ResourceDependency(library = "org.richfaces", name = "AutocompleteBase.js"),
@ResourceDependency(library = "org.richfaces", name = "Autocomplete.js"),
@ResourceDependency(library = "org.richfaces", name = "Autocomplete.ecss") })
public abstract class AutocompleteRendererBase extends InputRendererBase implements MetaComponentRenderer {
private static final Logger LOGGER = RichfacesLogger.RENDERKIT.getLogger();
public JSReference getClientFilterFunction(UIComponent component) {
AbstractAutocomplete autocomplete = (AbstractAutocomplete) component;
String clientFilter = (String) autocomplete.getAttributes().get("clientFilterFunction");
if (clientFilter != null && clientFilter.length() != 0) {
return new JSReference(clientFilter);
}
return null;
}
// TODO nick - handle parameter
@SuppressWarnings("unchecked")
private Object saveVar(FacesContext context, String var) {
if (var != null) {
Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
return requestMap.get(var);
}
return null;
}
private void setVar(FacesContext context, String var, Object varObject) {
if (var != null) {
Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
requestMap.put(var, varObject);
}
}
protected void encodeItems(FacesContext facesContext, UIComponent component, List<Object> fetchValues) throws IOException {
AbstractAutocomplete comboBox = (AbstractAutocomplete) component;
AutocompleteEncodeStrategy strategy = getStrategy(component);
strategy.encodeItemsContainerBegin(facesContext, component);
Object savedVar = saveVar(facesContext, comboBox.getVar());
Map<String, String> requestParameters = facesContext.getExternalContext().getRequestParameterMap();
String value = requestParameters.get(component.getClientId(facesContext) + "Value");
Iterator<Object> itemsIterator = comboBox.getItems(facesContext, value).iterator();
if (!itemsIterator.hasNext()) {
strategy.encodeFakeItem(facesContext, component);
} else {
while (itemsIterator.hasNext()) {
Object item = itemsIterator.next();
setVar(facesContext, comboBox.getVar(), item);
this.encodeItem(facesContext, comboBox, item, strategy);
if (comboBox.getFetchValue() != null) {
fetchValues.add(comboBox.getFetchValue().toString());
} else if (item != null) {
fetchValues.add(item.toString());
}
}
}
setVar(facesContext, comboBox.getVar(), savedVar);
strategy.encodeItemsContainerEnd(facesContext, component);
}
protected void encodeItemsContainer(FacesContext facesContext, UIComponent component) throws IOException {
AutocompleteEncodeStrategy strategy = getStrategy(component);
AutocompleteMode mode = (AutocompleteMode) component.getAttributes().get("mode");
if (mode != null && mode == AutocompleteMode.client) {
List<Object> fetchValues = new ArrayList<Object>();
this.encodeItems(facesContext, component, fetchValues);
this.encodeFetchValues(facesContext, component, fetchValues);
} else {
strategy.encodeItemsContainerBegin(facesContext, component);
// TODO: is it needed
// strategy.encodeFakeItem(facesContext, component);
strategy.encodeItemsContainerEnd(facesContext, component);
}
}
private void encodeFetchValues(FacesContext facesContext, UIComponent component, List<Object> fetchValues)
throws IOException {
if (!fetchValues.isEmpty()) {
ResponseWriter writer = facesContext.getResponseWriter();
writer.startElement(HtmlConstants.SCRIPT_ELEM, component);
writer.writeAttribute(HtmlConstants.TYPE_ATTR, "text/javascript", null);
JSObject script = new JSObject("RichFaces.ui.Autocomplete.setData", component.getClientId(facesContext) + "Items",
fetchValues);
writer.writeText(script, null);
writer.endElement(HtmlConstants.SCRIPT_ELEM);
}
}
private int getLayoutChildCount(AbstractAutocomplete component) {
int count = component.getChildCount();
for (UIComponent c : component.getChildren()) {
// do not switch from default strategy if just attachQueue or placeholder is present
if (c instanceof AbstractAttachQueue || c instanceof AbstractPlaceholder) {
count -= 1;
}
}
return count;
}
public void encodeItem(FacesContext facesContext, AbstractAutocomplete comboBox, Object item,
AutocompleteEncodeStrategy strategy) throws IOException {
ResponseWriter writer = facesContext.getResponseWriter();
if (getLayoutChildCount(comboBox) > 0) {
strategy.encodeItem(facesContext, comboBox);
} else {
if (item != null) {
strategy.encodeItemBegin(facesContext, comboBox);
writer.writeAttribute(HtmlConstants.CLASS_ATTRIBUTE, "rf-au-itm rf-au-opt rf-au-fnt rf-au-inp", null);
writer.writeText(item, null);
strategy.encodeItemEnd(facesContext, comboBox);
}
}
}
private AutocompleteEncodeStrategy getStrategy(UIComponent component) {
AbstractAutocomplete comboBox = (AbstractAutocomplete) component;
if (comboBox.getLayout() != null) {
if (comboBox.getLayout().equals(AutocompleteLayout.div.toString())) {
return new AutocompleteDivLayoutStrategy();
}
if (comboBox.getLayout().equals(AutocompleteLayout.list.toString())) {
return new AutocompleteListLayoutStrategy();
}
if (comboBox.getLayout().equals(AutocompleteLayout.table.toString())) {
return new AutocompleteTableLayoutStrategy();
}
}
return new AutocompleteDivLayoutStrategy();
}
@Override
protected void doDecode(FacesContext context, UIComponent component) {
AbstractAutocomplete autocomplete = (AbstractAutocomplete) component;
if (InputUtils.isDisabled(autocomplete)) {
return;
}
Map<String, String> requestParameters = context.getExternalContext().getRequestParameterMap();
String value = requestParameters.get(component.getClientId(context) + "Input");
if (value != null) {
autocomplete.setSubmittedValue(value);
}
if (requestParameters.get(component.getClientId(context) + ".ajax") != null) {
PartialViewContext pvc = context.getPartialViewContext();
pvc.getRenderIds().add(
component.getClientId(context) + MetaComponentResolver.META_COMPONENT_SEPARATOR_CHAR
+ AbstractAutocomplete.ITEMS_META_COMPONENT_ID);
context.renderResponse();
}
}
public void encodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) throws IOException {
if (AbstractAutocomplete.ITEMS_META_COMPONENT_ID.equals(metaComponentId)) {
List<Object> fetchValues = new ArrayList<Object>();
PartialResponseWriter partialWriter = context.getPartialViewContext().getPartialResponseWriter();
partialWriter.startUpdate(getStrategy(component).getContainerElementId(context, component));
encodeItems(context, component, fetchValues);
partialWriter.endUpdate();
if (!fetchValues.isEmpty()) {
Map<String, Object> dataMap = ExtendedPartialViewContext.getInstance(context).getResponseComponentDataMap();
dataMap.put(component.getClientId(context), fetchValues);
}
} else {
throw new IllegalArgumentException(metaComponentId);
}
}
public void decodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) {
throw new UnsupportedOperationException();
}
protected int getMinCharsOrDefault(UIComponent component) {
int value = 1;
if (component instanceof AbstractAutocomplete) {
value = ((AbstractAutocomplete) component).getMinChars();
if (value < 1) {
value = 1;
}
}
return value;
}
private Converter getConverterForValue(FacesContext context, UIComponent component) {
Converter converter = ((ValueHolder) component).getConverter();
if (converter == null) {
ValueExpression expression = component.getValueExpression("value");
if (expression != null) {
Class<?> containerClass = ServiceTracker.getService(context, GenericsIntrospectionService.class)
.getContainerClass(context, expression);
converter = InputUtils.getConverterForType(context, containerClass);
}
}
return converter;
}
@Override
public Object getConvertedValue(FacesContext context, UIComponent component, Object val) throws ConverterException {
String s = (String) val;
Converter converter = getConverterForValue(context, component);
if (converter != null) {
return converter.getAsObject(context, component, s);
} else {
return s;
}
}
}