package pt.ist.renderers.extensions; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import pt.ist.renderers.InputRenderer; import pt.ist.renderers.components.HtmlActionLink; import pt.ist.renderers.components.HtmlBlockContainer; import pt.ist.renderers.components.HtmlComponent; import pt.ist.renderers.components.HtmlContainer; import pt.ist.renderers.components.HtmlHiddenField; import pt.ist.renderers.components.HtmlSimpleValueComponent; import pt.ist.renderers.components.HtmlTextInput; import pt.ist.renderers.components.controllers.HtmlActionLinkController; import pt.ist.renderers.components.controllers.HtmlController; import pt.ist.renderers.components.converters.Converter; import pt.ist.renderers.components.state.IViewState; import pt.ist.renderers.contexts.PresentationContext; import pt.ist.renderers.extensions.validators.MultiLanguageStringValidator; import pt.ist.renderers.layouts.Layout; import pt.ist.renderers.model.MetaObject; import pt.ist.renderers.model.MetaSlot; import pt.ist.renderers.model.MetaSlotKey; import pt.ist.renderers.utils.RenderKit; import pt.ist.renderers.utils.RenderUtils; import pt.ist.renderers.validators.HtmlValidator; import pt.utl.ist.fenix.tools.util.Pair; import pt.utl.ist.fenix.tools.util.i18n.Language; import pt.utl.ist.fenix.tools.util.i18n.MultiLanguageString; /** * This renderer provides a generic way of editing slots that contain a * {@link MultiLanguageString}. The interface generated allows the user to * incrementally add more values in different languages. The user can also * remove some of the values already introduced. *

* Example:

Remove
Remove
Add
* * @author cfgi */ public class MultiLanguageStringInputRenderer extends InputRenderer { private Integer size; private String eachClasses; private String inputClasses; public Integer getSize() { return this.size; } /** * Allows you to configure the size of the input fields for each language. * * @property */ public void setSize(Integer size) { this.size = size; } public String getEachClasses() { return this.eachClasses; } /** * The classes to apply to the div containing each language line. * * @property */ public void setEachClasses(String eachClasses) { this.eachClasses = eachClasses; } public String getInputClasses() { return this.inputClasses; } /** * The classes to apply to the input field. * * @property */ public void setInputClasses(String inputClasses) { this.inputClasses = inputClasses; } public static class LanguageBean implements Serializable { public Language language; public String value; public LanguageBean(Language language, String value) { super(); this.language = language; this.value = value; } public static String exportAsString(LanguageBean languageBean) { StringBuilder builder = new StringBuilder(); if (languageBean != null) { builder.append(languageBean.language != null ? languageBean.language.toString() : ""); builder.append(":"); builder.append(languageBean.value != null ? languageBean.value : ""); } return builder.toString(); } public static String exportAsString(Collection languageBeans) { StringBuilder builder = new StringBuilder(); if (languageBeans != null) { for (LanguageBean bean : languageBeans) { String beanString = exportAsString(bean); builder.append(beanString.replace("/", "//")); builder.append("/"); } } return builder.toString(); } public static LanguageBean importFromString(String value) { if (value == null || value.length() == 0) { return null; } int firstIndex = value.indexOf(":"); if (firstIndex == -1) { return null; } String language = value.substring(0, firstIndex); String message = value.substring(firstIndex + 1); Language realLanguage = language.length() == 0 ? null : Language.valueOf(language); return new LanguageBean(realLanguage, message); } public static Collection importAllFromString(String value) { Collection allLanguageBeans = new ArrayList(); if (value == null || value.length() == 0) { return allLanguageBeans; } int startIndex = 0; int lastIndex = 0; int index; while (lastIndex < value.length()) { index = value.indexOf("/", lastIndex); if (index == -1) { return allLanguageBeans; } if (index < value.length() - 1 && value.charAt(index + 1) == '/') { lastIndex = index + 2; continue; } String part = value.substring(startIndex, index); allLanguageBeans.add(importFromString(part.replace("//", "/"))); lastIndex = index + 1; startIndex = lastIndex; } return allLanguageBeans; } } protected HtmlSimpleValueComponent getInputComponent() { HtmlTextInput textInput = new HtmlTextInput(); textInput.setSize(getSize() == null ? null : String.valueOf(getSize())); return textInput; } protected void configureInputComponent(HtmlSimpleValueComponent textInput) { } protected void configureLanguageContainer(HtmlContainer languageContainer, HtmlSimpleValueComponent input, HtmlSimpleValueComponent languageComponent, HtmlActionLink removeLink) { languageContainer.addChild(input); languageContainer.addChild(languageComponent); languageContainer.addChild(removeLink); } protected Converter getConverter() { return new MultiLanguageStringConverter(); } @Override protected Layout getLayout(Object object, Class type) { MetaObject metaObject = getInputContext().getMetaObject(); if (metaObject != null && metaObject instanceof MetaSlot) { MetaSlot metaSlot = (MetaSlot) metaObject; if (!metaSlot.hasValidator()) { Class defaultValidator = MultiLanguageStringValidator.class; metaSlot.setValidators(Collections.singletonList(new Pair, Properties>(defaultValidator, new Properties()))); } } return new MultiLanguageStringInputLayout(); } protected HtmlBlockContainer getTopContainer() { return new HtmlBlockContainer(); } protected class MultiLanguageStringInputLayout extends Layout { private static final String STATE_MAP_NAME = "mlsMap"; private static final String STATE_INDEX = "lastIndex"; protected Map getLanguageMap(boolean create) { Map map = (Map) getInputContext().getViewState().getLocalAttribute( STATE_MAP_NAME); if (map == null && create) { map = new Hashtable(); getInputContext().getViewState().setLocalAttribute(STATE_MAP_NAME, map); } return map; } protected String getLocalName(String part) { return getInputContext().getMetaObject().getKey() + "/" + part; } protected Integer updateLastIndex(Integer index) { Integer lastIndex = (Integer) getInputContext().getViewState().getLocalAttribute(STATE_INDEX); if (lastIndex == null || lastIndex < index) { lastIndex = index; getInputContext().getViewState().setLocalAttribute(STATE_INDEX, index); } return lastIndex; } @Override public HtmlComponent createComponent(Object object, Class type) { MultiLanguageString mls = (MultiLanguageString) object; MetaSlotKey key = ((MetaSlot) getInputContext().getMetaObject()).getKey(); HtmlBlockContainer container = getTopContainer(); // hidden field with real value HtmlHiddenField hiddenField = new HtmlHiddenField(); hiddenField.setTargetSlot(key); hiddenField.setController(new MultiLanguageStringController()); hiddenField.setConverter(getConverter()); container.addChild(hiddenField); // add link HtmlActionLink addLink = new HtmlActionLink(RenderUtils.getResourceString("renderers.language.add")); addLink.setName(getLocalName("add")); container.addChild(addLink); Map map = getLanguageMap(false); if ((map == null || map.isEmpty()) && (mls != null && !mls.isEmpty())) { map = getLanguageMap(true); int index = 0; for (Language language : mls.getAllLanguages()) { map.put(index++, new LanguageBean(language, mls.getContent(language))); } } HtmlActionLink firstRemoveLink = null; HtmlActionLink secondRemoveLink = null; if (map != null) { List> list = new ArrayList>(map.entrySet()); Collections.sort(list, new Comparator>() { @Override public int compare(Entry o1, Entry o2) { return o1.getKey().compareTo(o2.getKey()); } }); for (Map.Entry entry : list) { HtmlActionLink link = addLanguageInput(container, entry.getKey(), entry.getValue().value, entry.getValue().language, list.size() > 1); if (firstRemoveLink == null) { firstRemoveLink = link; } else if (secondRemoveLink == null) { secondRemoveLink = link; } } } else { // default: present one entry without allowing to remove addLanguageInput(container, 0, "", null, false); } // setup controllers to avoid displaying the remove link when only // one line is present addLink.setController(new AddNewLanguageController(container, firstRemoveLink)); if (map != null && map.size() == 2) { ((RemoveLanguageController) firstRemoveLink.getController()).setLink(secondRemoveLink); ((RemoveLanguageController) secondRemoveLink.getController()).setLink(firstRemoveLink); } return container; } private HtmlActionLink addLanguageInput(HtmlContainer container, Integer index, String value, Language language, boolean allowRemove) { // insert empty entry if not present Map map = getLanguageMap(true); if (!map.containsKey(index)) { map.put(index, new LanguageBean(null, null)); } updateLastIndex(index); // create component line HtmlContainer inputContainer = new HtmlBlockContainer(); inputContainer.setClasses(getEachClasses()); HtmlSimpleValueComponent textInput = getInputComponent(); textInput.setClasses(getInputClasses()); textInput.setName(getLocalName("text/" + index)); textInput.setValue(value); configureInputComponent(textInput); PresentationContext context = getInputContext().createSubContext(getInputContext().getMetaObject()); context.setProperties(new Properties()); Language usedLanguage = language == null ? Language.getUserLanguage() : language; HtmlSimpleValueComponent languageComponent = (HtmlSimpleValueComponent) RenderKit.getInstance().render(context, usedLanguage, Language.class); languageComponent.setController(new UpdateLanguageController(textInput, index)); languageComponent.setTargetSlot(null); languageComponent.setName(getLocalName("language/" + index)); HtmlActionLink removeLink = new HtmlActionLink(RenderUtils.getResourceString("renderers.language.remove")); removeLink.setVisible(allowRemove); removeLink.setName(getLocalName("remove/" + index)); removeLink.setController(new RemoveLanguageController(container, inputContainer, index)); configureLanguageContainer(inputContainer, textInput, languageComponent, removeLink); container.getChildren().add(container.getChildren().size() - 2, inputContainer); return removeLink; } private class MultiLanguageStringController extends HtmlController { @Override public void execute(IViewState viewState) { String value = null; Map map = getLanguageMap(false); if (map != null && map.size() > 0) { value = LanguageBean.exportAsString(map.values()); } HtmlSimpleValueComponent component = (HtmlSimpleValueComponent) getControlledComponent(); component.setValue(value == null ? null : value.toString()); } } private class UpdateLanguageController extends HtmlController { private final HtmlSimpleValueComponent input; private final Integer index; private UpdateLanguageController(HtmlSimpleValueComponent textInput, Integer index) { super(); this.input = textInput; this.index = index; } @Override public void execute(IViewState viewState) { Map map = getLanguageMap(true); HtmlSimpleValueComponent component = (HtmlSimpleValueComponent) getControlledComponent(); String value = this.input.getValue(); Language finalLanguage = component.getValue() == null || component.getValue().length() == 0 ? null : Language .valueOf(component.getValue()); map.put(this.index, new LanguageBean(finalLanguage, value)); } } private class AddNewLanguageController extends HtmlActionLinkController { private final HtmlBlockContainer container; private final HtmlActionLink link; public AddNewLanguageController(HtmlBlockContainer container, HtmlActionLink link) { this.container = container; this.link = link; } @Override public void linkPressed(IViewState viewState, HtmlActionLink link) { viewState.setSkipValidation(true); Integer index = updateLastIndex(0); updateLastIndex(index++); if (this.link != null) { this.link.setVisible(true); } addLanguageInput(this.container, index, "", null, true); } } private class RemoveLanguageController extends HtmlActionLinkController { private final HtmlContainer container; private final HtmlContainer inputContainer; private HtmlActionLink link; private final Integer index; public RemoveLanguageController(HtmlContainer container, HtmlContainer inputContainer, Integer index) { this.container = container; this.inputContainer = inputContainer; this.index = index; } public void setLink(HtmlActionLink link) { this.link = link; } @Override public void linkPressed(IViewState viewState, HtmlActionLink link) { viewState.setSkipValidation(true); this.container.removeChild(this.inputContainer); if (this.link != null) { this.link.setVisible(false); } Map map = getLanguageMap(true); map.remove(this.index); } } } public static class MultiLanguageStringConverter extends Converter { @Override public Object convert(Class type, Object value) { String text = (String) value; MultiLanguageString mls = new MultiLanguageString(); Collection allLanguageBean = LanguageBean.importAllFromString(text); for (LanguageBean bean : allLanguageBean) { if (bean.value != null && bean.value.trim().length() != 0) { mls.setContent(bean.language, bean.value); } } return mls; } } }