package pt.ist.renderers.extensions; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.collections.Predicate; 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.HtmlMultipleHiddenField; import pt.ist.renderers.components.HtmlTable; import pt.ist.renderers.components.HtmlTableRow; import pt.ist.renderers.components.controllers.HtmlActionLinkController; import pt.ist.renderers.components.state.IViewState; import pt.ist.renderers.contexts.PresentationContext; import pt.ist.renderers.extensions.converters.DomainObjectKeyArrayConverter; import pt.ist.renderers.layouts.Layout; import pt.ist.renderers.layouts.TabularLayout; import pt.ist.renderers.model.MetaObject; import pt.ist.renderers.model.MetaObjectFactory; import pt.ist.renderers.model.MetaSlot; import pt.ist.renderers.model.MetaSlotKey; import pt.ist.renderers.schemas.Schema; import pt.ist.renderers.utils.RenderKit; import pt.ist.renderers.utils.RenderMode; import pt.ist.renderers.utils.RenderUtils; /** * This renderer allows you get a list as input but delegates part of the * list's management to an external action. Each one of the slot's values * will be presented in an html list. The renderer manages the removal of * values from the list. So a delete action link will appear next to each * object's presentation. An add action link will allow the user to start the * external use-case that will add a new value to the list. * *

* Example: *

* Add * * @author cfgi */ public class ManageableListRenderer extends InputRenderer { public static final String MANAGED_SLOT_NAME = ManageableListRenderer.class.getName() + "/slot/name"; public static final String MANAGED_SLOT_KEY = ManageableListRenderer.class.getName() + "/slot/key"; private String destination; private String eachSchema; private String eachLayout; public String getEachLayout() { return this.eachLayout; } /** * The layout to be used when presenting each object. * * @property */ public void setEachLayout(String eachLayout) { this.eachLayout = eachLayout; } public String getEachSchema() { return this.eachSchema; } /** * The schema to be used when presenting each object. * * @property */ public void setEachSchema(String eachSchema) { this.eachSchema = eachSchema; } public String getDestination() { return this.destination; } /** * The destination for were control will be forwarded whn pressing * the add button. The destiny action is responsible for keeping the * current viewstate during the interaction. The action * is also responsible for changing the viewstate to reflect the intended * changes, that is, adding a new value to the managed list, before * sending the control back to the inicial page. * *

* The action can obtain a representation of the viewstate suitable for * including in a hidden field by doing: *

     *   IViewState viewState = RenderUtils.getViewState();
     *   String encoded = ViewState.encodeToBase64(viewState); 
     * 
* Retrieving the viewstate from it's encoded state and preparing before * sending control to the inicial page can be done with: *
     *   IViewState viewState = ViewState.decodeFromBase64(<encoded viewstate>);  
     *   viewState.setUser(UserIdentityFactory.create(request)); 
     *   // update slot in viewstate
     *   RenderUtils.setViewState(viewState);
     * 
* * Updating the viewstate consists of adding a new value to the slot's. * This must only be done through the meta-object contained in the viewstate: *
     *   MetaSlot slot = <find slot in viewState.getMetaObject().getSlots()>;
     *   List values = new ArrayList((List) slot.getObject());
     *   values.add(<the new value>);
     *   slot.setObject(values);
     * 
* * The slot that was beeing managed is available in viewstate attributes. You can * get information about the slot with: *
     *   String slotName = viewState.getAttribute(ManageableListRenderer.MANAGED_SLOT_NAME);
     *   MetaSlotKey slotKey = viewState.getAttribute(ManageableListRenderer.MANAGED_SLOT_KEY);
     * 
* * @property */ public void setDestination(String destination) { this.destination = destination; } @Override protected Layout getLayout(Object object, Class type) { MetaObject metaObject = getInputContext().getMetaObject(); return new ManageableListLayout((Collection) metaObject.getObject()); } private class ManageableListLayout extends TabularLayout { private final List objects; private final HtmlMultipleHiddenField hiddenValues; public ManageableListLayout(Collection collection) { this.objects = new ArrayList(collection); this.hiddenValues = new HtmlMultipleHiddenField(); this.hiddenValues.setTargetSlot((MetaSlotKey) getInputContext().getMetaObject().getKey()); // HACK: severe dependecy with fenix project this.hiddenValues.setConverter(new DomainObjectKeyArrayConverter()); } @Override protected int getNumberOfColumns() { return 2; } @Override protected int getNumberOfRows() { return this.objects == null ? 0 : this.objects.size(); } @Override protected HtmlComponent getHeaderComponent(int columnIndex) { return null; } @Override public HtmlComponent createComponent(Object object, Class type) { HtmlComponent component = super.createComponent(object, type); HtmlContainer container = new HtmlBlockContainer(); container.addChild(component); container.addChild(this.hiddenValues); HtmlActionLink link = new HtmlActionLink(); link.setName(getInputContext().getMetaObject().getKey().toString() + "/add"); link.setText(RenderUtils.getResourceString("renderers.list.management.add")); link.setController(new FollowDestinationController((MetaSlot) getInputContext().getMetaObject())); container.addChild(link); return container; } @Override public void applyStyle(HtmlComponent component) { HtmlContainer container = (HtmlContainer) component; super.applyStyle(container.getChildren().get(0)); } @Override protected HtmlComponent getComponent(int rowIndex, int columnIndex) { Object object = this.objects.get(rowIndex); Schema schema = RenderKit.getInstance().findSchema(getEachSchema()); String layout = getEachLayout(); MetaObject metaObject = MetaObjectFactory.createObject(object, schema); if (columnIndex == 0) { this.hiddenValues.addValue(metaObject.getKey().toString()); PresentationContext newContext = getContext().createSubContext(metaObject); newContext.setLayout(layout); newContext.setRenderMode(RenderMode.getMode("output")); RenderKit kit = RenderKit.getInstance(); return kit.render(newContext, object); } else { HtmlActionLink link = new HtmlActionLink(); String prefix = getInputContext().getMetaObject().getKey().toString(); link.setName(prefix + "/delete/" + rowIndex); link.setText(RenderUtils.getResourceString("renderers.list.management.delete")); HtmlTableRow row = getTable().getRows().get(rowIndex); link.setController(new RemoveLineController(getTable(), this.hiddenValues, row, prefix)); return link; } } } class RemoveLineController extends HtmlActionLinkController { private final HtmlTable table; private final HtmlTableRow row; private final HtmlMultipleHiddenField values; private final String prefix; public RemoveLineController(HtmlTable table, HtmlMultipleHiddenField values, HtmlTableRow row, String prefix) { this.table = table; this.values = values; this.row = row; this.prefix = prefix; } @Override public void linkPressed(IViewState viewState, HtmlActionLink link) { int index = this.table.getRows().indexOf(row); this.table.removeRow(this.row); this.values.removeValue(index); renameLinks(); } private void renameLinks() { List links = this.table.getChildren(new Predicate() { public boolean evaluate(Object object) { return object instanceof HtmlActionLink; } }); int pos = 0; for (HtmlComponent component : links) { ((HtmlActionLink) component).setName(this.prefix + "/delete/" + pos++); } } } class FollowDestinationController extends HtmlActionLinkController { private final MetaSlot slot; public FollowDestinationController(MetaSlot slot) { this.slot = slot; } @Override public void linkPressed(IViewState viewState, HtmlActionLink link) { if (getDestination() != null) { viewState.setAttribute(ManageableListRenderer.MANAGED_SLOT_NAME, slot.getName()); viewState.setAttribute(ManageableListRenderer.MANAGED_SLOT_KEY, slot.getKey()); viewState.setCurrentDestination(getDestination()); } } } }