/**
* Copyright © 2002 Instituto Superior Técnico
*
* This file is part of FenixEdu Academic.
*
* FenixEdu Academic 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 3 of the License, or
* (at your option) any later version.
*
* FenixEdu Academic 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 FenixEdu Academic. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fenixedu.academic.ui.faces.components;
import java.io.IOException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.IntegerConverter;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.CharEncoding;
import org.fenixedu.academic.util.Bundle;
import org.fenixedu.bennu.core.domain.User;
import org.fenixedu.bennu.core.i18n.BundleUtil;
import org.fenixedu.bennu.core.security.Authenticate;
import pt.ist.fenixframework.DomainObject;
import pt.ist.fenixframework.FenixFramework;
public class UIAutoComplete extends UIInput {
private static final String AUTO_COMPLETE_VALUE_HIDDEN_FIELD_SUFFIX = "_AutoComplete";
private static final String AUTO_COMPLETE_DIV_COMPONENT_SUFFIX = "_AutoCompleteDiv";
private static final String INIT_SCRIPT_FLAG_REQUEST_KEY = "___FENIX_AUTOCOMPLETE_SCRIPT_INIT";
private static final String DEFAULT_ENCODING = CharEncoding.UTF_8;
private static final String DEFAULT_AUTO_COMPLETE_SERVLET_URI = "ajax/AutoCompleteServlet";
private static final String INVALID_AUTO_COMPLETE_INPUT =
"org.fenixedu.academic.ui.faces.validators.autoCompleteValidator.INVALID_AUTO_COMPLETE_INPUT";
private static final String AUTO_COMPLETE_VALUE_REQUIRED =
"org.fenixedu.academic.ui.faces.validators.autoCompleteValidator.AUTO_COMPLETE_VALUE_REQUIRED";
public UIAutoComplete() {
setRendererType(null);
setConverter(new IntegerConverter());
}
@Override
public void encodeEnd(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
Map requestMap = context.getExternalContext().getRequestMap();
String valueHiddenClientId = this.getClientId(context) + AUTO_COMPLETE_VALUE_HIDDEN_FIELD_SUFFIX;
String inputTextClientId = this.getClientId(context);
String divClientId = this.getClientId(context) + AUTO_COMPLETE_DIV_COMPONENT_SUFFIX;
String autoCompleteStyleClass = (String) this.getAttributes().get("autoCompleteStyleClass");
String autoCompleteItemsStyleClass = (String) this.getAttributes().get("autoCompleteItemsStyleClass");
String className = (String) this.getAttributes().get("className");
String inputTextArgName = (String) this.getAttributes().get("inputTextArgName");
String labelField = (String) this.getAttributes().get("labelField");
String valueField = (String) this.getAttributes().get("valueField");
String serviceName = (String) this.getAttributes().get("serviceName");
String serviceArgs = (String) this.getAttributes().get("serviceArgs");
String value = (String) this.getValue();
String textFieldStyleClass = (String) this.getAttributes().get("textFieldStyleClass");
Integer size = (Integer) this.getAttributes().get("size");
String contextPath = ((HttpServletRequest) context.getExternalContext().getRequest()).getContextPath();
String inputTextValue = (value != null) ? getInputTextValue(getUserView(context), value, labelField) : null;
encodeAutoCompleteInclusionScriptsIfRequired(writer, requestMap, contextPath);
encodeInputTextField(writer, inputTextClientId, inputTextValue, textFieldStyleClass, size);
// encodeAutoCompleteDiv(writer, divClientId, autoCompleteStyleClass);
encodeValueHiddenField(writer, valueHiddenClientId, value);
encodeAutoCompleteInitializationScript(writer, inputTextClientId, divClientId, contextPath, serviceName, serviceArgs,
labelField, valueField, autoCompleteItemsStyleClass, className, inputTextArgName);
}
private User getUserView(FacesContext context) {
return Authenticate.getUser();
}
private String getInputTextValue(User userView, String externalId, String labelField) {
try {
DomainObject domainObject = FenixFramework.getDomainObject(externalId);
return BeanUtils.getProperty(domainObject, labelField);
} catch (Exception ex) {
throw new RuntimeException("Error getting value for autocomplete component", ex);
}
}
private void encodeAutoCompleteInitializationScript(ResponseWriter writer, String inputTextClientId, String divClientId,
String contextPath, String serviceName, String serviceArgs, String labelField, String valueField,
String autoCompleteItemsStyleClass, String className, String inputTextArgName) throws IOException {
String finalUri =
MessageFormat.format(contextPath + "/" + DEFAULT_AUTO_COMPLETE_SERVLET_URI
+ "?args={0}&labelField={1}&valueField={2}&styleClass={3}&class={4}&inputTextArgName={5}", new Object[] {
"serviceName=" + serviceName + ",serviceArgs=" + URLEncoder.encode(serviceArgs, DEFAULT_ENCODING),
labelField, valueField, autoCompleteItemsStyleClass, className, inputTextArgName });
String escapeId = escapeId(inputTextClientId);
String scriptText =
"$(\"input#"
+ escapeId
+ "\").autocomplete(\""
+ finalUri
+ "\", { minChars: 3"
+ ", validSelection: false"
+ ", cleanSelection: clearAutoComplete, select: selectElement, after: updateCustomValue, error:showError}); +\n"
+ "$(\"input[name='" + escapeId + "']\").val($(\"input#" + escapeId + "\").val());";
writer.startElement("script", null);
writer.writeAttribute("language", "JavaScript", null);
writer.write(scriptText);
writer.endElement("script");
}
protected String escapeId(String textFieldId) {
return textFieldId.replace(".", "\\\\.").replaceAll(":", "\\\\\\\\:");
}
private void encodeAutoCompleteDiv(ResponseWriter writer, String clientId, String autoCompleteStyleClass) throws IOException {
writer.startElement("div", this);
writer.writeAttribute("class", autoCompleteStyleClass, null);
writer.writeAttribute("id", clientId, null);
writer.writeAttribute("name", clientId, null);
writer.endElement("div");
}
private void encodeAutoCompleteInclusionScriptsIfRequired(ResponseWriter writer, Map requestMap, String contextPath)
throws IOException {
if (requestMap.containsKey(INIT_SCRIPT_FLAG_REQUEST_KEY) == false) {
String javaScriptBasePath = contextPath + "/javaScript";
writer.startElement("script", null);
writer.writeAttribute("type", "text/javascript", null);
writer.writeAttribute("language", "JavaScript", null);
writer.writeAttribute("src", javaScriptBasePath + "/autoComplete.js", null);
writer.endElement("script");
writer.startElement("script", null);
writer.writeAttribute("type", "text/javascript", null);
writer.writeAttribute("language", "JavaScript", null);
writer.writeAttribute("src", javaScriptBasePath + "/autoCompleteHandlers.js", null);
writer.endElement("script");
requestMap.put(INIT_SCRIPT_FLAG_REQUEST_KEY, true);
}
}
private void encodeValueHiddenField(ResponseWriter writer, String clientId, String value) throws IOException {
writer.startElement("input", this);
writer.writeAttribute("type", "hidden", null);
writer.writeAttribute("id", clientId, null);
writer.writeAttribute("name", clientId, null);
writer.writeAttribute("value", (value != null) ? value : "", null);
writer.endElement("input");
}
private void encodeInputTextField(ResponseWriter writer, String clientId, String value, String styleClass, Integer size)
throws IOException {
writer.startElement("input", this);
writer.writeAttribute("type", "text", null);
writer.writeAttribute("id", clientId, null);
writer.writeAttribute("name", clientId, null);
writer.writeAttribute("value", (value != null) ? value : "", null);
writer.writeAttribute("class", (styleClass != null) ? styleClass : "", null);
writer.writeAttribute("size", (size != null) ? size : "", null);
writer.writeAttribute("onkeyup", "javascript:autoCompleteClearValueFieldIfTextIsEmpty('" + clientId + "');", null);
writer.endElement("input");
}
@Override
public void decode(FacesContext context) {
Map requestMap = context.getExternalContext().getRequestParameterMap();
String valueFieldClientId = this.getClientId(context) + AUTO_COMPLETE_VALUE_HIDDEN_FIELD_SUFFIX;
String value = (String) requestMap.get(valueFieldClientId);
setSubmittedValue(value);
this.setValid(true);
}
@Override
protected void validateValue(FacesContext context, Object newValue) {
String submittedInputTextValue =
(String) context.getExternalContext().getRequestParameterMap().get(this.getClientId(context));
if (submittedInputTextValue.length() == 0 && newValue == null) {
if (isValid() && isRequired()) {
String errorMessage = getMessageFromBundle(context, AUTO_COMPLETE_VALUE_REQUIRED);
context.addMessage(getClientId(context),
new FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage, errorMessage));
this.setValid(false);
}
} else if (submittedInputTextValue.length() != 0 && newValue == null) {
String errorMessage = getMessageFromBundle(context, INVALID_AUTO_COMPLETE_INPUT);
context.addMessage(getClientId(context), new FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage, errorMessage));
this.setValid(false);
} else {
// Value needs to be validated
String className = (String) this.getAttributes().get("className");
String labelField = (String) this.getAttributes().get("labelField");
try {
DomainObject domainObject = FenixFramework.getDomainObject((String) newValue);
String correctLabelForExternalId = BeanUtils.getProperty(domainObject, labelField);
if (correctLabelForExternalId.equals(submittedInputTextValue) == false) {
String errorMessage = getMessageFromBundle(context, INVALID_AUTO_COMPLETE_INPUT);
context.addMessage(getClientId(context), new FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage,
errorMessage));
this.setValid(false);
}
} catch (Exception ex) {
String errorMessage = "Unexpected error occured in validation";
context.addMessage(getClientId(context),
new FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage, errorMessage));
this.setValid(false);
}
}
}
private String getMessageFromBundle(FacesContext context, String key) {
return BundleUtil.getString(Bundle.APPLICATION, key);
}
}