<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/fenix-renderers.tld" prefix="fr" %>

The third situation: renderers meet actions

How do I collect random pieces of information?

When you use the edit tag you are always editing an object. So you must wrap all the pieces of information in a bean and then edit that bean.

Can you give an example?

Suppose you, for some strange reason, need the user to introduce two ages, a date, and a gender to search for persons. You could create the following bean:

public class SearchBean implements Serializable {
    private int minAge;
    private int maxAge;
    private Date date;
    private Gender gender;

    public int getMinAge() {
        return this.minAge;
    }

    public void setMinAge(int minAge) {
        this.minAge = minAge;
    }

    public int getMaxAge() {
        return this.maxAge;
    }

    public void setMaxAge(int maxAge) {
        this.maxAge = maxAge;
    }

    public Date getDate() {
        return this.date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Gender getGender() {
        return this.gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }
}

And in some action you create the bean and put it in a request attribute.

SearchBean bean = new SearchBean();
bean.setDate(new Date());

request.setAttribute("bean", bean);

But how do we send the input to the action that will search the persons? For that you use the action attribute of the edit tag. This attribute makes the form submit the content to the specified action. By default the form is submited to the input location, that is, the url originally requested.

<fr:edit name="bean" action="/renderers/searchPersons.do?method=search"/>

Now we need to know how, in the target action, we get access to the bean. Between interactions with the user the view state is mantained. Is through that view state the we can get the object that was edited. So in the action we would do something like:

ViewState viewState = (ViewState) RenderUtils.getViewState();
SearchBean bean = (SearchBean) viewState.getMetaObject().getObject();

// search person
(...)

All we need now is the working example. What I mean by "working" is that the pieces of code presented are executed and not that it will search persons.

I need to control the form

So you need to pass some extra hidden fields to the action, use some properties from a Struts action form, or even put the form submission buttons in other place and with other names? Can you still do this?

Yes!

What you need to do is surround the renderer tag with an html:form tag. When you do this no form will be generated by the renderer. This means that the Submit and Cancel buttons you are used to see next to the renderer input presentation will not be present and you have to manually add new buttons. This gives you more control about the form and, in fact, is the only way of changing the name of the submit button for just one page and to introduce hidden fields for actions.

If you don't use the html:form but use a plain form instead then you have to manually indicate that the renderization is nested in another form. For this you can use the nested attribute of the edit or create tag. If you specify nested=true then not form will be generating automatically no mather were the fr:edit or fr:create is beeing used.

<html:form action="/renderers/searchPersons.do?method=search">
    <html:hidden property="theProperty" value="theValue"/>
    
    <fr:edit name="bean" action="/renderers/searchPersons.do?method=search"/>
    <html:submit value="Save"/>
    <html:cancel value="Forget"/>
</html:form>

The outter form now defines the target action but if some controler chooses to send the flow to other destination they will still override the destination specified in the form. The behaviour of the "Cancel" button that is normally generated when the fr:edit and fr:create are used can be reintroduced with the html:cancel tag. Renderers recognize Struts semantic for that button and also cancel the viewstate processing.

Intercepting the submission and changing the destination

Now imagine that you want to do something to the submited data before the action is executed or even choose the destination according to the user input. How can you do that? For this type of control you need to use controllers.

Controllers can be associated to form components and are executed just after the components are updated with the submited values. Each controller has access to the corresponding controlled component and to the view state. This way they can influence the global structure or aspect of what is presented, control the lifecycle of the components, redirect to another location, etc.

Controllers can only be associated to components by renderers and not from the configuration like validators. So if you need to include controllers you probably need to create a new renderer. Here is an example of a controller that redirects to different locations according to gender:

class ChooseFromGenderController extends HtmlController {

    @Override
    public void execute(IViewState viewState) {
        HtmlMenu genderMenu = (HtmlMenu) getControlledComponent();
        
        Gender gender = (Gender) genderMenu.getConvertedValue(Gender.class);
        if (Gender.MALE.equals(gender)) {
            viewState.setCurrentDestination("male");
        }
        else {
            viewState.setCurrentDestination("female");
        }
    }
    
}

Now there is something there that needs explaining. Were do we define those names in setCurrentDestination? The edit tag supports an inner tag named destination that allows you to define destinations, or exit point, from the generated form. So the use of the edit tag would be a little different:

<fr:edit name="bean" action="/renderers/searchPersons.do?method=search">
    <fr:destination name="female" path="/renderers/searchFemales."/>
    <fr:destination name="male" path="/renderers/searchMales.do"/>
</fr:edit>

There is also a default behaviour that is important to know. By default there are three destination names that are used if provided:

"success"
is used if the submission is valid and has no errors
"invalid"
is used when some validation fails
"cancel"
is used when the cancel button is pressed

The next example shows a controller that capitalizes the text of a field. This controller needs to be associated to a submit button to be executed properlly. The behaviour inherited from HtmlSubmitButtonController makes this controller be executed only when the button is pressed and changes the submission lifecycle to not alter the object.

class CapitalizeController extends HtmlSubmitButtonController {

    private HtmlSimpleValueComponent input;
    
    public CapitalizeController(HtmlSimpleValueComponent component) {
        this.input = component;
    }

    @Override
    protected void buttonPressed(IViewState viewState, HtmlSubmitButton button) {
        String text = this.input.getValue();
        this.input.setValue(capitalize(text));
    }

    private String capitalize(String text) {
        StringBuilder buffer = new StringBuilder();
        char ch, prevCh;
        
        prevCh = ' ';
        for (int i = 0;  i < text.length();  i++) {
           ch = text.charAt(i);
           
           if (Character.isLetter(ch)  &&  !Character.isLetter(prevCh)) {
               buffer.append(Character.toUpperCase(ch));
           }
           else {
               buffer.append(ch);
           }
           
           prevCh = ch;
        }
        
        return buffer.toString();
    }
    
}

Controllers and renderer's can save it's own attributes in the ViewState

Sometimes for a more complex implementation of a controller or renderer you need to store some values between requests of the user. The ViewState object allows you to set attributes that are persisted (in the client side) between requests. The view state attributes are global, that is, if you set the attribute "a" in one renderer then all renderers and controllers that are executed next will see that attribute. Nevetheless the default input context implementation tries make attributes local to each renderer. The objective is to avoid name conflicts and make the implementation easier.

The next example shows an additional controllers that change the value of a field to it's uppercase. This controller is defined as a inner class of a renderer so it has access to the input context and consequently to the view state. It uses this fact to maintain the controller in the right state even after the values are submited and the object is changed. This controller also interacts with the previous controller to make the capitalize button visible only when the value is in lower case.

class CaseChangeController extends HtmlSubmitButtonController {

    private HtmlSubmitButton button;
    private HtmlSubmitButton capitalize;
    private HtmlSimpleValueComponent input;
    
    public CaseChangeController(HtmlSimpleValueComponent component, HtmlSubmitButton button, HtmlSubmitButton capitalizeButton) {
        this.input = component;
        this.button = button;
        this.capitalize = capitalizeButton;
        
        setupButtons();
    }

    private void setupButtons() {
        this.button.setText(isUpperCase() ? "To Upper Case" : "To Lower Case");
        this.capitalize.setVisible(isUpperCase());
    }
    
    private boolean isUpperCase() {
        if (getInputContext().getViewState().getAttribute("isUpperCase") == null) {
            return true;
        }
        
        return ((Boolean) getInputContext().getViewState().getAttribute("isUpperCase")).booleanValue();
    }
    
    private void setUpperCase(boolean isUpperCase) {
        getInputContext().getViewState().setAttribute("isUpperCase", new Boolean(isUpperCase));
    }
    
    @Override
    protected void buttonPressed(IViewState viewState, HtmlSubmitButton button) {
        String text = this.input.getValue();
        this.input.setValue(isUpperCase() ? text.toUpperCase() : text.toLowerCase());
        
        setUpperCase(!isUpperCase());
        setupButtons();
    }
}

To use this two new controllers we need to create a custom renderer but that will only be shown latter. Right now all we need to know is that a new layout was defined and associated with the implemented renderer. Now lets see the working example.

Schema

<schema name="person.create-minimal-defaults" type="net.sourceforge.fenixedu.domain.Person">
    <slot name="name" layout="allow-case-change"/>
</schema>

Code

<fr:edit id="case-change" name="UserView" property="person" layout="tabular" schema="person.name"/>

Result

Creating components directly in the actions

There is another form of collection random pieces of data from the user but it probably still requires a bean to hold those random pieces. Nevertheless this new aproach allows you to have more control of some of the presentation aspects behind the renderers framework.

The idea is to allow components to be created directly from the action. This way, in the action, we can create custom components, compose them at will, and use them directly in the JSP to form the presentation. This use of components is always composed of three steps:

  1. Creating a ViewState
  2. Obtaining a schema
  3. Wrapping the bean or holder of the information in a MetaObject
  4. Creating components and compose them
  5. Binding input components to specific slots
  6. Setup controllers and converters
  7. Make the ViewState available in the request

Let't watch a simple but complete example and then explain the details:

Code in action

// 1
ActionViewState viewState = new ActionViewState("testing");

// 2
Schema schema = RenderKit.getInstance().findSchema("schema");

// 3
MetaObject metaObject = MetaObjectFactory.createObject(bean, schema);
viewState.setMetaObject(metaObject);

// 4
final HtmlTextInput input = new HtmlTextInput();

// 5
String aSlotName = schema.getSlotDescriptions().get(0).getSlotName();
input.bind(metaObject.getSlot(aSlotName));

// 6
input.setController(new HtmlController() {

    @Override
    public void execute(IViewState viewState) {
        System.out.println("changing the text to lowercase");
        
        if (input.getValue() != null) {
            input.setValue(input.getValue().toLowerCase());
        }
    }
    
});

input.setConverter(new Converter() {

    @Override
    public List<String> convert(Class type, Object value) {
        if (value == null) {
            return null;
        }
        else {
            return Arrays.asList(((String) value).split("\\p{Space}+"));
        }
    }
    
});

// 7
request.setAttribute("customNamesInput", viewState);

Code in JSP

<fr:viewstate name="customNamesInput" action="/example.do?method=collect"/>

Code in Action (handle submit)

ViewState viewState = RenderUtils.getViewState("testing");
HtmlTextInput component = (HtmlTextInput) viewState.getComponent();
UsedBean bean = (UsedBean) viewState.getMetaObject().getObject();

List<String> names1 = (List<String<) component.getConvertedValue();
List<String> names2 = bean.getNamesList();

Specialized ViewState

As you may notice we started by creating a specific ViewState. The ActionViewState is a specialization of the ViewState commonly available, that can be used directly by actions and that skips some steps in the renderers lifecycle.

This ViewState has the same role as the other automatically created by the fr:edit and fr:create tags. The essential about the ViewState is that it must contain a component. Optionally it can contain a meta object if some component is bound to a slot, that is, the ViewState must contain the meta object containing the slots to wich component were bound.

There aren't other requirements over the ViewState. After setting it up you just need to make it available to a JSP, usually by setting a request attribute.

Creating a MetaObject as an abstraction of the real object

When you have an object and want to associate some input components to slots of that object then you need to create an abstraction of that object called the meta object. This meta object it the one used by the renderers framework and is usefull to help serializing objects and prevent changes to object until they really need to be made.

To create a meta object you normally should obtain a schema. The schema limits the slots that the meta object will contain but you don't really need the schema. If you pass null as a schema then all the object's slots are available in the meta object. Schemas can be obtained through the RenderKit object. Schemas are found by their name in the configuration.

After you created the meta object don't forget to set the ViewState with it.

Binding slots to components

When you want to associate some input component's value to a slot as it normally happens in the fr:edit and fr:create tags you just need to select the desired meta slot from the create meta object and bind the input component to it. After doing this, the final value of the component, after the user submited the form, will be set in the object.

Off course, you can only by one input component to a meta slot. You can bind several input component to the same slot but all well get is having the setter being called several times.

Do we really nead a bean?

As you may have noticed, you don't really need the bean in the example. If you don't bind any input component to a slot then you don't need to create the meta object and so you won't need the object/bean. Nevertheless you will have to obtain the value directly from the input component and probably have to take measures to make them easy to find like giving them specific ids.

You may need a bean when you want some logic associated with the retrievel or processing of the information or just to concentrate all the information in an object easy to pass to other parts of the program but it's never required.

Converters and controllers in custom components

The converter and controller you see in the example are just ther to make a point, they don't really make anything usefull. But as you can see you can associate any piece of code that is executed afer the input of the user is processed. This can be used, for example, to make a preprocessing of values or to copy some intermediate values to a final location. Controllers can also change the lifecycle through the ViewState provided so they can be used for a lot of purposes.

As mentioned a typical use is to use the values of several fields and put a computed value in a final field that normally has converter associated. For example you can provide 3 input fields and grab those three values to make a parseable time that you put in a 4th field.

Convertes are used because all the values of the components are strings or arrays of strings. The converter allows to request the final value for the slot having the conversion been specified beforehand. The converter is specially usefull when an input component is bound to a slot. You need to add a converter whenever the value of the component cannot be trivially converter into the slot's type.

Top Next: The fourth situation: a new renderer