/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <hr>
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* This file has been modified by the OpenOLAT community. Changes are licensed
* under the Apache 2.0 license as the original file.
* <p>
*/
package org.olat.core.gui.components.form.flexible.impl.elements;
import java.util.ArrayList;
import org.apache.commons.lang.StringEscapeUtils;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.ComponentRenderer;
import org.olat.core.gui.components.form.flexible.FormBaseComponentIdProvider;
import org.olat.core.gui.components.form.flexible.elements.InlineTextElement;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.FormJSHelper;
import org.olat.core.gui.render.RenderResult;
import org.olat.core.gui.render.Renderer;
import org.olat.core.gui.render.RenderingState;
import org.olat.core.gui.render.StringOutput;
import org.olat.core.gui.render.URLBuilder;
import org.olat.core.gui.translator.Translator;
import org.olat.core.helpers.Settings;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.ValidationStatus;
/**
* Description:<br>
* TODO: patrickb Class Description for TextElement
* <P>
* Initial Date: 25.11.2006 <br>
*
* @author patrickb
*/
public class TextElementImpl extends AbstractTextElement implements InlineTextElement {
protected TextElementComponent component;
//set text input type as default
private String htmlInputType = HTML_INPUT_TYPE_TEXT;
public final static String HTML_INPUT_TYPE_TEXT = "text";
public final static String HTML_INPUT_TYPE_PASSWORD = "password";
//inline stuff
protected String transientValue;//last submitted value, which may be good or wrong
private static final OLog log = Tracing.createLoggerFor(TextElementImpl.class);
/**
* @param id A fix identifier for state-less behavior, must be unique or null
*/
public TextElementImpl(String id, String name, String predefinedValue) {
this(id, name, predefinedValue, HTML_INPUT_TYPE_TEXT);
}
/**
* TODO: check if htmlInputType is valid
* @param id A fix identifier for state-less behavior, must be unique or null
* @param name
* @param predefinedValue
* @param htmlInputType
*/
public TextElementImpl(String id, String name, String predefinedValue, String htmlInputType) {
this(id, name, predefinedValue, htmlInputType, false);
}
/**
* @param id A fix identifier for state-less behavior, must be unique or null
* @param name
* @param predefinedValue
* @param asInline
*/
public TextElementImpl(String id, String name, String predefinedValue, boolean asInline) {
super(id, name, asInline);
if(asInline){
initInlineEditing(predefinedValue);
}else{
component = new TextElementComponent(id, this);
}
}
/**
* for specialized TextElements, i.e. IntegerElementImpl.
* @param id A fix identifier for state-less behavior, must be unique or null
* @param name
*/
protected TextElementImpl(String id, String name){
//if you change something here, please see if other constructors need a change too.
super(name);
component = new TextElementComponent(id, this);
}
/**
* @param id A fix identifier for state-less behavior, must be unique or null
* @param name
* @param predefinedValue
* @param htmlInputType
* @param asInlineEditingElement
*/
public TextElementImpl(String id, String name, String predefinedValue, String htmlInputType, boolean asInlineEditingElement){
super(id, name, asInlineEditingElement);
setValue(predefinedValue);
if(HTML_INPUT_TYPE_TEXT.equals(htmlInputType) || HTML_INPUT_TYPE_PASSWORD.equals(htmlInputType)) {
this.htmlInputType = htmlInputType;
} else {
throw new AssertException(htmlInputType + " html input type not supported!");
}
if(asInlineEditingElement) {
initInlineEditing(predefinedValue);
} else {
// init the standard element component
component = new TextElementComponent(id, this);
}
}
private void initInlineEditing(String predefinedValue) {
// init the inline editing element component.
transientValue = predefinedValue;
AbstractInlineElementComponent aiec = new AbstractInlineElementComponent(this, new ComponentRenderer() {
public void renderHeaderIncludes(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, Translator translator,
RenderingState rstate) {
// nothing to do here
}
public void renderBodyOnLoadJSFunctionCall(Renderer renderer, StringOutput sb, Component source, RenderingState rstate) {
// nothing to do here
}
public void render(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, Translator translator,
RenderResult renderResult, String[] args) {
AbstractInlineElementComponent aiec = (AbstractInlineElementComponent) source;
InlineTextElement itei = (InlineTextElement) aiec.getInlineElement();
StringBuilder htmlVal = new StringBuilder();
/**
* in case of an error show the test which caused the error which must be stored by the textelement in the transientValue.
* the last valid value is always set over setValue(..) by the textelement, and thus can be retrieved as such here.
*/
String tmpVal;
String emptyVal = (itei.isInlineEditingOn() ? "" : itei.getEmptyDisplayText());
if(itei.hasError()){
tmpVal = StringHelper.containsNonWhitespace(transientValue) ? transientValue : emptyVal;
}else{
tmpVal = StringHelper.containsNonWhitespace(getValue()) ? getValue() : emptyVal;
}
// append the html safe value
htmlVal.append(StringEscapeUtils.escapeHtml(tmpVal));
if (!itei.isEnabled()) {
// RO view and not clickable
String id = aiec.getFormDispatchId();
sb.append("<div class='form-control-static' id=\"").append(id).append("\" ")
.append(" >").append(htmlVal).append("</div>");
} else {
//
// Editable view
// which can be left
// .......with clicking outside -> onBlur saves the value
// .......pressing ENTER/RETURN or TAB -> onBlur saves the value
// .......presssing ESC -> restore previous value and submit this one.
if (itei.isInlineEditingOn()) {
String id = aiec.getFormDispatchId();
// read write view
sb.append("<input type=\"").append("input").append("\" class=\"form-control\" id=\"");
sb.append(id);
sb.append("\" name=\"");
sb.append(id);
sb.append("\" size=\"");
sb.append("30");
// if(itei.maxlength > -1){
// sb.append("\" maxlength=\"");
// sb.append(itei.maxlength);
// }
sb.append("\" value=\"");
sb.append(htmlVal);
sb.append("\" ");
sb.append(" />");
// Javascript
sb.append(FormJSHelper.getJSStart());
// clicking outside or pressing enter -> OK, pressing ESC -> Cancel
FormJSHelper.getInlineEditOkCancelJS(sb, id, StringEscapeUtils.escapeHtml(getValue()), itei.getRootForm());
sb.append(FormJSHelper.getJSEnd());
} else {
// RO<->RW view which can be clicked
Translator trans = Util.createPackageTranslator(TextElementImpl.class, translator.getLocale(), translator);
String id = aiec.getFormDispatchId();
sb.append("<div id='").append(id).append("' class='form-control-static' title=\"")
.append(StringEscapeUtils.escapeHtml(trans.translate("inline.edit.help")))
.append("\" ")
.append(FormJSHelper.getRawJSFor(itei.getRootForm(), id, itei.getAction()))
.append("> ")
.append(htmlVal)
.append(" <i class='o_icon o_icon_inline_editable'> </i></div>");
}
}//endif
}
});
setInlineEditingComponent(aiec);
}
/**
* @see org.olat.core.gui.components.form.flexible.FormItemImpl#evalFormRequest(org.olat.core.gui.UserRequest)
*/
@Override
public void evalFormRequest(UserRequest ureq) {
if(isInlineEditingElement()){
//evalFormRequestInline(ureq);
}else {
evalFormRequestStandard();
}
}
private void evalFormRequestStandard() {
String paramId = component.getFormDispatchId();
String paramValue = getRootForm().getRequestParameter(paramId);
if (paramValue != null) {
setValue(paramValue);
// mark associated component dirty, that it gets rerendered
component.setDirty(true);
}
}
@Override
protected void dispatchFormRequest(UserRequest ureq) {
if(isInlineEditingElement()){
dispatchFormRequestInline(ureq);
}else {
super.dispatchFormRequest(ureq);
}
}
protected void dispatchFormRequestInline(UserRequest ureq) {
// click to go back display mode only -> submit -> trigger formOk -> saving
// value(s)
String paramId = String.valueOf(((FormBaseComponentIdProvider)getInlineEditingComponent()).getFormDispatchId());
String paramVal = getRootForm().getRequestParameter(paramId);
if (paramVal != null) {
// if value has changed -> set new value and submit
// otherwise nothing has changed, just switch the inlinde editing mode.
//validate the inline element to check for error
transientValue = getValue();
setValue(paramVal);
validate(new ArrayList<ValidationStatus>());
if(hasError()){
//in any case, if an error is there -> set Inline Editing on
isInlineEditingOn(true);
}
getRootForm().submit(ureq);//submit validates again!
if(hasError()){
setValue(transientValue);//error with paramVal -> fallback to previous
}
transientValue = paramVal;//this value shows in error case up in inline field along with error
}
if(!hasError()){
if (isInlineEditingOn()) {
isInlineEditingOn(false);
} else {
isInlineEditingOn(true);
}
}
// mark associated component dirty, that it gets rerendered
getInlineEditingComponent().setDirty(true);
}
@Override
protected Component getFormItemComponent() {
return component;
}
protected String getHtmlInputType() {
return htmlInputType;
}
@Override
public void setDomReplacementWrapperRequired(boolean required) {
if(component != null) {
component.setDomReplacementWrapperRequired(required);
}
}
@Override
public void setTranslator(Translator translator) {
// wrap package translator with fallback form translator
// hint: do not take this.getClass() but the real class! for package translator creation
Translator elmTranslator = Util.createPackageTranslator(TextElementImpl.class,
translator.getLocale(), translator);
super.setTranslator(elmTranslator);
}
/**
* DO NOT USE THE ONCHANGE EVENT with TEXTFIELDS!
* @see org.olat.core.gui.components.form.flexible.impl.FormItemImpl#addActionListener(org.olat.core.gui.control.Controller, int)
* TODO: add an onkeypress listener which will post do background instead, this could then also be used for an autocomplete textfield
*/
@Override
public void addActionListener(int action) {
super.addActionListener(action);
if (action == FormEvent.ONCHANGE && Settings.isDebuging()) {
log.warn("Do not use the onChange event in Textfields / TextAreas as this has often unwanted side effects. " +
"As the onchange event is only tiggered when you click outside a field or navigate with the tab to the next element " +
"it will suppress the first attempt to the submit click as by clicking " +
"the submit button first the onchange event will be triggered and you have to click twice to submit the data. ");
}
}
}