/**
* 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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.FormItemDependencyRule;
import org.olat.core.gui.components.form.flexible.FormLayouter;
import org.olat.core.gui.components.form.flexible.FormMultipartItem;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Disposable;
import org.olat.core.gui.render.velocity.VelocityRenderDecorator;
import org.olat.core.gui.translator.PackageTranslator;
import org.olat.core.gui.translator.Translator;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.ValidationStatus;
/**
* Description:<br>
*
* <P>
* Initial Date: 22.11.2006 <br>
*
* @author patrickb
*/
public class FormLayoutContainer extends FormItemImpl implements FormItemContainer, FormLayouter, Disposable {
private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(FormLayoutContainer.class);
private static final String LAYOUT_DEFAULT = VELOCITY_ROOT + "/form_default.html";
private static final String LAYOUT_DEFAULT_6_6 = VELOCITY_ROOT + "/form_default_6_6.html";
private static final String LAYOUT_DEFAULT_9_3 = VELOCITY_ROOT + "/form_default_9_3.html";
private static final String LAYOUT_DEFAULT_2_10 = VELOCITY_ROOT + "/form_default_2_10.html";
private static final String LAYOUT_HORIZONTAL = VELOCITY_ROOT + "/form_horizontal.html";
private static final String LAYOUT_VERTICAL = VELOCITY_ROOT + "/form_vertical.html";
private static final String LAYOUT_BAREBONE = VELOCITY_ROOT + "/form_barebone.html";
private static final String LAYOUT_BUTTONGROUP = VELOCITY_ROOT + "/form_buttongroup.html";
private static final String LAYOUT_INPUTGROUP = VELOCITY_ROOT + "/form_inputgroup.html";
private static final String LAYOUT_PANEL = VELOCITY_ROOT + "/form_panel.html";
/**
* manage the form components of this form container
*/
private VelocityContainer formLayoutContainer;
/**
* formComponents and formComponentNames are managed together, change something here needs a change there.
* formComponents contain the FormItem based on their name
* formComponentsNames is use in the velocity to render according to the registered name.
* The addXXX method adds elements ->
* The register method register an element only -> used for setErrorComponent / setLabelComponent.
*/
private Map<String,FormItem> formComponents;
private List<String> formComponentsNames;
private Map<String,FormItem> listeningOnlyFormComponents;
private boolean hasRootForm=false;
private Map<String, Map<String, FormItemDependencyRule>> dependencyRules;
/**
* Form layout is provided by caller, access the form item to render inside
* the velocity container by
* <ul>
* <li>name to get the form field</li>
* <li>name_LABEL</li>
* <li>name_ERROR</li>
* <li>name_EXAMPLE</li>
* </ul>
* You can also access form item information like
* <ul>
* <li>$f.hasError(name)</li>
* <li>$f.hasLabel(name)</li>
* <li>$f.hasExample(name)</li>
* </ul>
* which helps you for layouting the form correct.
*
* @param name
* @param translator
*/
private FormLayoutContainer(String name, Translator formTranslator, String page) {
this(null, name, formTranslator, page);
}
private FormLayoutContainer(String id, String name, Translator formTranslator, String page) {
super(id, name, false);
formLayoutContainer = new VelocityContainer(id == null ? null : id + "_VC", name, page, formTranslator, null);
if (page.equals(LAYOUT_DEFAULT) || page.equals(LAYOUT_VERTICAL) || page.equals(LAYOUT_HORIZONTAL) || page.equals(LAYOUT_BUTTONGROUP) || page.equals(LAYOUT_INPUTGROUP)) {
// optimize for lower DOM element count - provides its own DOM ID in velocity template
formLayoutContainer.setDomReplacementWrapperRequired(false);
}
translator = formTranslator;
// add the form decorator for the $f.hasError("ddd") etc.
formLayoutContainer.contextPut("f", new FormDecorator(this));
// this container manages the form items, the GUI form item componentes are
// managed in the associated velocitycontainer
formComponentsNames = new ArrayList<String>(5);
formLayoutContainer.contextPut("formitemnames", formComponentsNames);
formComponents = new HashMap<String, FormItem>();
listeningOnlyFormComponents = new HashMap<String, FormItem>();
dependencyRules = new HashMap<String, Map<String, FormItemDependencyRule>>();
}
/**
* @see org.olat.core.gui.components.form.flexible.FormComponent#rememberFormRequest(org.olat.core.gui.UserRequest,
* long[], int)
*/
public void evalFormRequest(UserRequest ureq) {
// form layouter has no values to store temporary
}
@Override
public void validate(List<ValidationStatus> validationResults) {
// form layouter is not validating
}
@Override
public void reset() {
// form layouter can not be resetted
}
@Override
protected void rootFormAvailable() {
// could initialize all formComponents with rootform
// simpler -> you can not add before adding rootform
hasRootForm = true;
}
/**
* @see org.olat.core.gui.components.form.flexible.FormContainer#add(java.lang.String,
* org.olat.core.gui.components.form.flexible.FormComponentImpl)
*/
public void add(FormItem formComp) {
add(formComp.getName(), formComp);
}
@Override
public void add(String name, FormItem formComp) {
if(!hasRootForm){
throw new AssertionError("first ensure that the layout container knows about its rootform!!");
}
// set the formtranslator, and parent
Translator itemTranslator = formComp.getTranslator();
if (itemTranslator != null && itemTranslator instanceof PackageTranslator) {
// let the FormItem provide a more specialized translator
PackageTranslator itemPt = (PackageTranslator) itemTranslator;
itemTranslator = PackageTranslator.cascadeTranslators(itemPt, translator);
} else {
itemTranslator = translator;
}
formComp.setTranslator(itemTranslator);
formComp.setRootForm(getRootForm());
//
String formCompName = name;
// book keeping of FormComponent order
formComponentsNames.add(formCompName);
formComponents.put(formCompName, formComp);
/*
* add the gui representation
*/
formLayoutContainer.put(formCompName, formComp.getComponent());
formLayoutContainer.put(formCompName + FormItem.ERRORC, formComp.getErrorC());
formLayoutContainer.put(formCompName + FormItem.EXAMPLEC, formComp.getExampleC());
formLayoutContainer.put(formCompName + FormItem.LABELC, formComp.getLabelC());
// Check for multipart data, add upload limit to form
if (formComp instanceof FormMultipartItem) {
getRootForm().setMultipartEnabled(true);
}
}
@Override
public void add(String name, Collection<FormItem> foItems){
//remove if already in
if(formLayoutContainer.getContext().containsKey(name)){
//remove existing collection
formLayoutContainer.contextRemove(name);
//remove all associated form items
for (FormItem formItem : foItems) {
remove(formItem);
}
}
//make collection accessible with <name> in the container.
//collection contains then only the names.
List<String> foItemsCollectionAsNames = new ArrayList<String>();
formLayoutContainer.contextPut(name, foItemsCollectionAsNames);
//add all items as form items to the container
for (FormItem formItem : foItems) {
String foName = formItem.getName();
add(foName, formItem);
foItemsCollectionAsNames.add(foName);
}
}
/**
* register only, does not addsubcomponents, does not expose formItem in the velocity.
* In 99% of the cases you should use an addXX method instead.
* @param formComp
*/
public void register(FormItem formComp) {
if(!hasRootForm){
throw new AssertionError("first ensure that the layout container knows about its rootform!!");
}
// set the formtranslator, and parent
Translator itemTranslator = formComp.getTranslator();
if(formComp.getTranslator()!=null && itemTranslator instanceof PackageTranslator){
//let the FormItem provide a more specialized translator
PackageTranslator itemPt = (PackageTranslator)itemTranslator;
itemTranslator = PackageTranslator.cascadeTranslators(itemPt, translator);
}else{
itemTranslator = translator;
}
formComp.setTranslator(itemTranslator);
formComp.setRootForm(getRootForm());
String formCompName = formComp.getName();
// book keeping of FormComponent order
listeningOnlyFormComponents.put(formCompName, formComp);
}
@Override
public void remove(FormItem toBeRemoved) {
String formCompName = toBeRemoved.getName();
remove(formCompName, toBeRemoved);
}
public void replace(FormItem toBeReplaced, FormItem with){
String formCompName = toBeReplaced.getName();
int pos = formComponentsNames.indexOf(formCompName);
formComponentsNames.add(pos, with.getName());
formComponentsNames.remove(formCompName);
/*
* remove the gui representation
*/
formLayoutContainer.remove(toBeReplaced.getComponent());
formLayoutContainer.remove(toBeReplaced.getErrorC());
formLayoutContainer.remove(toBeReplaced.getExampleC());
formLayoutContainer.remove(toBeReplaced.getLabelC());
// set the formtranslator, and parent
Translator itemTranslator = with.getTranslator();
if(with.getTranslator()!=null && itemTranslator instanceof PackageTranslator){
//let the FormItem provide a more specialized translator
PackageTranslator itemPt = (PackageTranslator)itemTranslator;
itemTranslator = PackageTranslator.cascadeTranslators(itemPt, translator);
}else{
itemTranslator = translator;
}
with.setTranslator(itemTranslator);
with.setRootForm(getRootForm());
formComponents.put(formCompName, with);
/*
* add the gui representation
*/
formLayoutContainer.put(formCompName, with.getComponent());
formLayoutContainer.put(formCompName + FormItem.ERRORC, with.getErrorC());
formLayoutContainer.put(formCompName + FormItem.EXAMPLEC, with.getExampleC());
formLayoutContainer.put(formCompName + FormItem.LABELC, with.getLabelC());
// Check for multipart data, add upload limit to form
if (with instanceof FormMultipartItem) {
getRootForm().setMultipartEnabled(true);
}
}
/**
* remove the component with the give name from this container
* @param binderName
*/
public void remove(String formCompName) {
FormItem toBeRemoved = getFormComponent(formCompName);
remove(formCompName, toBeRemoved);
}
private void remove(String formCompName, FormItem toBeRemoved) {
// book keeping of FormComponent order
formComponentsNames.remove(formCompName);
formComponents.remove(formCompName);
/*
* remove the gui representation
*/
formLayoutContainer.remove(toBeRemoved.getComponent());
formLayoutContainer.remove(toBeRemoved.getErrorC());
formLayoutContainer.remove(toBeRemoved.getExampleC());
formLayoutContainer.remove(toBeRemoved.getLabelC());
}
public void moveBefore(FormItem itemToBeMoved, FormItem itemRef) {
int index = formComponentsNames.indexOf(itemToBeMoved.getName());
int indexRef = formComponentsNames.indexOf(itemRef.getName());
if(index > 0 && indexRef >= 0) {
String toMove = formComponentsNames.remove(index);
formComponentsNames.add(indexRef, toMove);
}
}
@Override
public VelocityContainer getFormItemComponent() {
return formLayoutContainer;
}
@Override
public Iterable<FormItem> getFormItems() {
List<FormItem> merged = new ArrayList<FormItem>(formComponents.values());
merged.addAll(listeningOnlyFormComponents.values());
return merged;
}
@Override
public Map<String, FormItem> getFormComponents() {
Map<String,FormItem> merged = new HashMap<String, FormItem>(formComponents);
merged.putAll(listeningOnlyFormComponents);
return Collections.unmodifiableMap(merged);
}
@Override
public boolean hasFormComponent(FormItem item) {
return formComponents.containsValue(item)
|| listeningOnlyFormComponents.containsValue(item);
}
public FormItem getFormComponent(String name){
if(formComponents.containsKey(name)) {
return formComponents.get(name);
}
return listeningOnlyFormComponents.get(name);
}
public void contextPut(String key, Object value) {
formLayoutContainer.contextPut(key, value);
}
public void contextRemove(String key) {
formLayoutContainer.contextRemove(key);
}
public Object contextGet(String key) {
return formLayoutContainer.contextGet(key);
}
public void put(String name, Component component) {
formLayoutContainer.put(name, component);
}
public void remove(Component component){
formLayoutContainer.remove(component);
}
public Component getComponent(String name) {
return formLayoutContainer.getComponent(name);
}
public boolean isDomReplacementWrapperRequired() {
return formLayoutContainer.isDomReplacementWrapperRequired();
}
public String getId(String prefix) {
return VelocityRenderDecorator.getId(prefix, formLayoutContainer);
}
@Override
public void setElementCssClass(String elementCssClass) {
formLayoutContainer.setElementCssClass(elementCssClass);
super.setElementCssClass(elementCssClass);
}
/**
* Set the translated title
* @param title
*/
public void setFormTitle(String title) {
formLayoutContainer.contextPut("off_title", title);
}
/**
* Set an icon to thte title
* @param iconCss
*/
public void setFormTitleIconCss(String iconCss) {
if (iconCss == null) {
formLayoutContainer.contextRemove("off_icon");
} else {
formLayoutContainer.contextPut("off_icon", iconCss);
}
}
/**
* Set the translated description
* @param description
*/
public void setFormDescription(String description) {
formLayoutContainer.contextPut("off_desc", description);
}
/**
* Set an optional context help link for this form. If you use a custom
* template this will have no effect
*
* @param url The page in confluence
*/
public void setFormContextHelp(String url) {
if (url == null) {
formLayoutContainer.contextRemove("off_chelp_url");
} else {
formLayoutContainer.contextPut("off_chelp_url", url);
}
}
/**
*
* @see org.olat.core.gui.components.form.flexible.FormLayouter#setDirty(boolean)
*/
public void setDirty(boolean dirty){
formLayoutContainer.setDirty(dirty);
}
/**
* @see org.olat.core.gui.components.form.flexible.api.FormItemContainer#addDependencyRule(org.olat.core.gui.components.form.flexible.api.FormItemDependencyRule)
*/
public void addDependencyRule(FormItemDependencyRule depRule) {
String key = depRule.getTriggerElement().getName();
Map<String, FormItemDependencyRule> rules;
if(dependencyRules.containsKey(key)){
//already rules for this element
rules = dependencyRules.get(key);
}else{
//no rules yet, create
rules = new HashMap<String, FormItemDependencyRule>();
dependencyRules.put(key, rules);
}
rules.put(depRule.getIdentifier(), depRule);
}
/**
* @see org.olat.core.gui.components.form.flexible.api.FormItemContainer#evalDependencyRuleSetFor(org.olat.core.gui.UserRequest, org.olat.core.gui.components.form.flexible.api.FormItem)
*/
public void evalDependencyRuleSetFor(UserRequest ureq, FormItem dispatchFormItem) {
String key = dispatchFormItem.getName();
if(dependencyRules.containsKey(key)){
Map<String, FormItemDependencyRule> ruleSet = dependencyRules.get(key);
Collection<FormItemDependencyRule> rules = ruleSet.values();
for (Iterator<FormItemDependencyRule> iter = rules.iterator(); iter.hasNext();) {
FormItemDependencyRule tmp = iter.next();
if (tmp.applyRule(this)) {
setDirty(true);
}
}
}
}
/**
* @see org.olat.core.gui.components.form.flexible.FormItemImpl#setEnabled(boolean)
*/
@Override
public void setEnabled(boolean isEnabled) {
//enable / disable this
super.setEnabled(isEnabled);
//iterate over all components and disable / enable them
for (FormItem element : getFormItems()) {
element.setEnabled(isEnabled);
}
}
/**
* Create a default layout container with the standard label - element alignment left.
*
* @param name
* @param formTranslator
* @return
*/
public static FormLayoutContainer createDefaultFormLayout(String name, Translator formTranslator){
FormLayoutContainer tmp = new FormLayoutContainer(name, formTranslator, LAYOUT_DEFAULT);
return tmp;
}
/**
* This a variant of the default form layout but with a ration 6 to 6 for label and field.
* @param name
* @param formTranslator
* @return
*/
public static FormLayoutContainer createDefaultFormLayout_6_6(String name, Translator formTranslator){
FormLayoutContainer tmp = new FormLayoutContainer(name, formTranslator, LAYOUT_DEFAULT_6_6);
return tmp;
}
/**
* This a variant of the default form layout but with a ration 9 to 3 for label and field.
* @param name
* @param formTranslator
* @return
*/
public static FormLayoutContainer createDefaultFormLayout_9_3(String name, Translator formTranslator){
FormLayoutContainer tmp = new FormLayoutContainer(name, formTranslator, LAYOUT_DEFAULT_9_3);
return tmp;
}
/**
* This a variant of the default form layout but with a ration 2 to 10 for label and field.
* @param name
* @param formTranslator
* @return
*/
public static FormLayoutContainer createDefaultFormLayout_2_10(String name, Translator formTranslator){
FormLayoutContainer tmp = new FormLayoutContainer(name, formTranslator, LAYOUT_DEFAULT_2_10);
return tmp;
}
/**
* Create a layout container that renders the form elements and its labels vertically.
*
* @param name
* @param formTranslator
* @return
*/
public static FormLayoutContainer createHorizontalFormLayout(String name, Translator formTranslator){
FormLayoutContainer tmp = new FormLayoutContainer(name, formTranslator, LAYOUT_HORIZONTAL);
return tmp;
}
/**
* Create a layout container that renders the form elements and its labels
* vertically. This means that the label of an element is forced to be on a
* separate line without any left indent.
*
* @param name
* @param formTranslator
* @return
*/
public static FormLayoutContainer createVerticalFormLayout(String name, Translator formTranslator){
FormLayoutContainer tmp = new FormLayoutContainer(name, formTranslator, LAYOUT_VERTICAL);
return tmp;
}
/**
* Create a layout container which only loop and render a list of components. There isn't
* any warning, error, label rendering.
* @param name
* @param formTranslator
* @return
*/
public static FormLayoutContainer createBareBoneFormLayout(String name, Translator formTranslator){
FormLayoutContainer tmp = new FormLayoutContainer(name, formTranslator, LAYOUT_BAREBONE);
return tmp;
}
/**
* Create a layout container based on the panel of bootstrap
* @param name
* @param formTranslator
* @return
*/
public static FormLayoutContainer createPanelFormLayout(String name, Translator formTranslator){
FormLayoutContainer tmp = new FormLayoutContainer(name, formTranslator, LAYOUT_PANEL);
return tmp;
}
/**
* Create a layout container that should be only used to render buttons using
* a o_button_group css wrapper. Buttons are ususally rendered on one line
* without indent
*
* @param name
* @param formTranslator
* @return
*/
public static FormLayoutContainer createButtonLayout(String name, Translator formTranslator) {
FormLayoutContainer tmp = new FormLayoutContainer(name, formTranslator, LAYOUT_BUTTONGROUP);
return tmp;
}
/**
* Create a layout container that should be only used to render input groups. Input groups are
* form items that are decorated with some left or right sided add-on. The add-on can be either
* a text (e.g. "@" to indicate an email address field) or an html i-tag with some OpenOLAT image
* classes. Alternatively, a second component can be added to the layout container with the name
* "leftAddOn" or "rightAddOn" to use the component as add-on (e.g. to implement a chooser link)
*
* @param name The name of the form layout container
* @param formTranslator the form translator
* @param leftTextAddOn the left side add-on text or NULL if not used
* @param rightTextAddOn the right side add-on text or NULL if not used
* @return the form layout container
*/
public static FormLayoutContainer createInputGroupLayout(String name, Translator formTranslator, String leftTextAddOn, String rightTextAddOn) {
FormLayoutContainer tmp = new FormLayoutContainer(name, formTranslator, LAYOUT_INPUTGROUP);
if (StringHelper.containsNonWhitespace(leftTextAddOn)) {
tmp.contextPut("leftAddOn", leftTextAddOn);
}
if (StringHelper.containsNonWhitespace(rightTextAddOn)) {
tmp.contextPut("rightAddOn", rightTextAddOn);
}
return tmp;
}
/**
*
* @param name
* @param formTranslator
* @param page
* @return
*/
public static FormLayoutContainer createCustomFormLayout(String name, Translator formTranslator, String page) {
return createCustomFormLayout(null, name, formTranslator, page);
}
public static FormLayoutContainer createCustomFormLayout(String id, String name, Translator formTranslator, String page) {
FormLayoutContainer tmp = new FormLayoutContainer(id, name, formTranslator, page);
return tmp;
}
@Override
public void setTranslator(Translator translator) {
super.setTranslator(translator);
// set also translator on velocity container delegate
this.formLayoutContainer.setTranslator(translator);
}
/**
* Dispose all child elements from this container
*
* @see org.olat.core.gui.control.Disposable#dispose()
*/
@Override
public void dispose() {
// Dispose also disposable form items (such as file uploads that needs to
// cleanup temporary files)
for (FormItem formItem : getFormItems()) {
if (formItem instanceof Disposable) {
Disposable disposableFormItem = (Disposable) formItem;
disposableFormItem.dispose();
}
}
}
}