<%@ 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 second situation: give me input

What is "input" exactly?

In the renderers' context "input" has a more specific meaning than normal. What we refer as input corresponds to a web interaction were the user is editing slots of a given object. The detail here is that the user is always, conceptually, editing an existing object. So gathering semi-unrelated pieces of data from the user to execute an action and show some result back cannot be done in exactly the same way.

Is this a limitation? No. It's just done differently — maybe in a more complex way for simplest cases — with the intention of making it quick and simple to edit domain objects and interact with complex input from the user.

But let's start with the basics

The main difference from all the previous examples is that for input we use a different tag: edit. As the name may sugest it allows you the create a generic presentation to edit an object. Changes in objects are important when they are persisted. This only happens directly for domain objects so lets start editing some slots of a person:

Schema

<schema name="person.simple-edit" type="net.sourceforge.fenixedu.domain.Person">
    <slot name="name">
        <property name="size" value="50"/>
    </slot>
    <slot name="gender"/>
    <slot name="idDocumentType"/>
    <slot name="documentIdNumber"/>
    <slot name="expirationDateOfDocumentIdYearMonthDay">
        <property name="size" value="12"/>
        <property name="maxLength" value="10"/>
    </slot>
    <slot name="availablePhoto"/>
</schema>

Code

<fr:edit id="input" name="UserView" property="person" schema="person.simple-edit">
    <fr:layout name="tabular">
        <fr:property name="classes" value="style1"/>
        <fr:property name="columnClasses" value="listClasses,,"/>
    </fr:layout>
</fr:edit>

Result

You can try and change the name. Change the last name, submit and you can observe the change in general navigation bar were it shows the first and last name of the current user (the one we are editing).

Let's validate the input before you do more harm

With the previous form you can already introduce some very strange data. The only things that are verified are the database constraints. If you specify a value that violates some contraint for a column then you will get an error that is not handled by the renderers. There are some exceptions that do not produce a crash but instead make the submission invalid. Currently this is the case of exceptions that are raised when a value is to be converted from the given input to the required type.

To avoid some of the problems you can include validators to each slot with the assumption that each slot value is represented by one input field. Validators are also declared in schemas. In each slot you can set the attribute validator with the name of the validator class. A validator will automatically be created and associated to the field. You will notice that the default presentation for a person input saves and extra column for validators to display themselves.

Schema

<schema name="person.simple-edit-validated" type="net.sourceforge.fenixedu.domain.Person">
    <slot name="name">
        <validator class="pt.ist.fenixWebFramework.renderers.validators.RegexpValidator">
            <property name="regexp" value="\p{Space}*[^ ]+\p{Space}+[^ ]+.*"/>
            <property name="message" value="Escreva pelo menos o primeiro e último nome"/>
            <property name="key" value="false"/>
        </validator>
        <property name="size" value="50"/>
    </slot>
    <slot name="gender" validator="pt.ist.fenixWebFramework.renderers.validators.RequiredValidator"/>
    <slot name="idDocumentType" validator="pt.ist.fenixWebFramework.renderers.validators.RequiredValidator"/>
    <slot name="documentIdNumber" validator="pt.ist.fenixWebFramework.renderers.validators.RequiredValidator"/>
    <slot name="expirationDateOfDocumentIdYearMonthDay">
        <validator class="pt.ist.fenixWebFramework.renderers.validators.RegexpValidator">
            <property name="regexp" value="\p{Digit}?\p{Digit}/\p{Digit}?\p{Digit}/(\p{Digit}\p{Digit})?\p{Digit}\p{Digit}"/>
        </validator>
        <property name="size" value="12"/>
        <property name="maxLength" value="10"/>
    </slot>
    <slot name="availablePhoto"/>
</schema>

Result

What is a validator?

A validator is a simple HtmlComponent. This means that, among other things, it's it can be included by the renderer in the generated structure like any other component. By default the presentation of a validator is it's error message but obviously you can override that.

How hard is it to create a new validator? Let's see an example:

public class RegexpValidator extends HtmlValidator {

    private String regexp;

    /**
     * Required constructor.
     */
    public RegexpValidator(Validatable component) {
        this(component, ".*");
    }

    public RegexpValidator(Validatable component, String regexp) {
        super(component);

        setRegexp(regexp);
        
        // default messsage
        setKey(true);
        setMessage("validator.regexp");
    }

    public String getRegexp() {
        return this.regexp;
    }

    public void setRegexp(String regexp) {
        this.regexp = regexp;
    }

    @Override
    protected String getResourceMessage(String message) {
        return RenderUtils.getResourceString(message, getRegexp());
    }

    @Override
    public void performValidation() {
        String text = getComponent().getValue();

        setValid(text.matches(getRegexp()));
    }

}
    

The first constructor is required to create the validator dinamically from the settings in schemas. The second constructor is used from renderers, that is, when the validator is always included by the renderer. The getters and setters allow you to specify the regexp from the schema's definition. The getResourceMessage was overriden to allow the resource string to display the regexp used. The main method, performValidation does what you would expect: gets the text from the component being validated, and sets this validator valid only if the component value mathes the regular expression.

How do we create new objects?

Currently, only domain objects can be created, that is, when you submit the presented form, and the form is valid, a new instance of the domain object is created and persisted in the database. Apart of that, the object creation proceeds in the same way as the previous examples.

Once again, the main difference is that you need to use a third tag: the tag create. With the create tag you only specify the type of the object to create but we want to create a new intance and not edit an existing one.

Schema

<schema name="person.create-minimal" type="net.sourceforge.fenixedu.domain.Person">
    <slot name="name">
        <validator class="pt.ist.fenixWebFramework.renderers.validators.RegexpValidator">
            <property name="regexp" value="\p{Space}*[^ ]+\p{Space}+[^ ]+.*"/>
            <property name="message" value="Escreva pelo menos o primeiro e último nome"/>
            <property name="key" value="false"/>
        </validator>
        <property name="size" value="50"/>
    </slot>
    <slot name="username"
          validator="pt.ist.fenixWebFramework.renderers.validators.RequiredValidator"/>
    <slot name="idDocumentType" 
          validator="pt.ist.fenixWebFramework.renderers.validators.RequiredValidator"/>
    <slot name="documentIdNumber" validator="pt.ist.fenixWebFramework.renderers.validators.RequiredValidator"/>
    <slot name="gender" validator="pt.ist.fenixWebFramework.renderers.validators.RequiredValidator"/>
    <slot name="maritalStatus" 
          validator="pt.ist.fenixWebFramework.renderers.validators.RequiredValidator"/>
    <slot name="isPassInKerberos"/>
</schema>

Code

<fr:create id="create" type="net.sourceforge.fenixedu.Person" schema="person.create-minimal">
    <fr:layout name="tabular">
        <fr:property name="classes" value="style1"/>
        <fr:property name="columnClasses" value="listClasses,,"/>
    </fr:layout>
</fr:create>

Result

You can now submit the form and if everything validates a new person will be created in the database. When the page is redisplayed all the values inserted are present in the form but you are still creating objects. Not editing the object that was created. This happens because the viewstate is preserved and the form presentation is associated with that viewstate.

Can I choose the default values?

As you may have noticed, all fields appear blank at first. This happens because we don't have an instance to provide the values, so new default values are presented. Nevertheless you can control part of this behaviour directly from the schema.

The slot's attribute default can be used to specify the default value for the slot when no value is available, as in this case. The specified value will be converted to the type of the slot and then presented. From example, for a enumeration you can specify as the default value the name of the enum. Here is an example with some default values:

Schema

(...)
    <slot name="username" default="pxxxxx" (...)
    <slot name="idDocumentType" default="IDENTITY_CARD" (...)
    <slot name="maritalStatus" default="UNKNOWN" (...)
(...)

Result

Hey! Default values in schemas are bad

Indeed they are. But they are simple and are directly tied to the schema, that is, to the place were you tell how you want the object to be presented. The default value, as presented before, allows you to statically specify values that are independent from the context were the schema is being used. This works reasonably well for enums or some strings but is less good for complex values that cannot be easily represented as a string.

So, you can also specify default values for slots directly in the page. To do this you need to use the tag fr:default inside a fr:create tag. There is only one required attribute for the fr:default tag. The slot attribute allows you to specify the name of the slot for which you are defining the default value. All other attributes allow you two ways to specify the concrete value.

You can use either the name, property, and scope attributes to select a value that is already accessible from the page or the value and converter attributes to create a value from a string. The last approach is similar to specifying the default value in the schema but allows you to create a dynamic default and indicate how to convert it. Off course, most of the attributes are optional. You can ommit property or scope, as you can ommit the converter, but you must always specify one of the name or value attributes.

Code

<fr:create id="dynamic-defaults" type="net.sourceforge.fenixedu.domain.Person" schema="person.create-minimal">
    <fr:layout name="tabular">
        <fr:property name="classes" value="style1"/>
        <fr:property name="columnClasses" value="listClasses,,"/>
    </fr:layout>
    
    <fr:default slot="name" name="UserView" property="person.name"/>
    <fr:default slot="username" value="<%= "p" + 12345 %>"/>
    <fr:default slot="idDocumentType" value="IDENTITY_CARD" 
                converter="pt.ist.fenixWebFramework.renderers.converters.EnumConverter"/>
    <fr:default slot="isPassInKerberos" value="true"/>
</fr:create>

Result

"/>

Note that you can only specify slots that were defined in the schema beeing used. The use of any other slot will result in an error. If you specify a value that is not of the same type of the slot you can get the error immediately or only after submission, there no garantie that the error will be detected early.

Can I create multiple objects at the same time?

Short answer: yes. Neverthless the creation of multiple objects at once is only possible in certain conditions. That conditions are:

This may seam limitative but convers many pratical cases. Normally you want to create an object but to mantain the domain coherence you need to create some additional objects. These auxiliary objects are normally only required if the relation has multiplicity 1. If the relation has a greater multiplicity then it's represented by a list and it's uncommon to require the list to be not empty. Specially since it's impossible to verify that directly in the database.

Let's give an example. Suppose you want to create a person and it's associated user, that the person has only one user, and that the user as a "setPlainPassword" method that encripts the password (I know it's a lot to assume :-).

<schema name="person.compund.create" type="net.sourceforge.fenixedu.domain.Person">
  <slot name="name"/>
  <slot name="email"/>
  <slot name="user.username"/>
  <slot name="user.plainPassword"/>
</schema>

If this schema is refered in a fr:create tag then you will be presented with four editors (one for each slot). In the inteface they will be presented as if they all belong to the person but you can see in the schema that the username and the virtual slot plainPassword belong to the user that should be assiaciated with the person.

When creating the person, the renderers will try to set the username and plainPassword of an User object that does not exist (because the person is now being created). In that case the user object will automatically created. It will be associated to the person and the the values will be set on the newly created User object.

This strategy has no limitation in the number of levels that a schema can refer. All the intermediary complex values will be created. Nevertheless it should be uncommon to need more that one level like in the example. Specially because you are limited with relations of multiplicity 1.

And if the user does not provide enough input?

Sometimes you want the user to fill some of the object's slots and want to fill the remaining slots yourself. Or some slots are dependant of the context. For example, if you are editing some structure and want to create a child, then the parent may be already determined from the context were the child is being created. So the user provides basic information for the child and you provide the parent automatically.

It's important to notice that I am not talking about creating the object and then set the value sof the slots that the user didn't provided. The situation here being described is the user submiting all the required information without having the capability to edit some of them. The techinque used is similar to the hidden fields techinque. The only difference is that those values are associated with slots.

Let's suppose we wan't to create a person and automatically attribute the same roles that the current user has. We would do this:

Code

<fr:create id="hidden" type="net.sourceforge.fenixedu.domain.Person" schema="person.create-minimal-defaults">
    <fr:hidden slot="personRoles" multiple="true" name="UserView" property="person.personRoles"/>
    
    <fr:layout name="tabular">
        <fr:property name="classes" value="style1"/>
        <fr:property name="columnClasses" value="listClasses,,"/>
    </fr:layout>
</fr:create>

Result

As you can see, we declare an hidden slot and say were the value is located in the same way as possible in the main tags. You can specify the name, property and scope to look for the value. You can also specify a conversion for the value. This is needed because every object will be translated to a string and the default conversion may not suffice for all cases. If the value is a DomainObject then a conversion is automatically provided.

Another important, but easy to miss, aspect of the example is the multiple attribute. This attribute indicates that the value is to be translated into a list, that is, that several values are, in fact, being provided for the same slot. If multiple was not provided then the slot would be set with a single value ... unless tha value was in fact a list. This is complicated and you have several possiblities to specify an hidden slot and how it should be handled.

Basically, if the given value is a Collection then multiple is assumed to be true for that slot. If you provide multiple declarations of the same slot then multiple is also set to true. In all the other cases multiple is considered to be false unless explicitly specified to be true. So, in the previous example the use of multiple was unnecessary. You can also verify that the example may not even work because the roles are set with an unspecified order. You can provide a specific order by examplicity give multiple values for the slot.

Code

<fr:create id="hidden" type="net.sourceforge.fenixedu.domain.Person" schema="person.create-minimal-defaults">
    <fr:hidden slot="personRoles" name="UserView" property="person.personRoles[1]"/>
    <fr:hidden slot="personRoles" name="UserView" property="person.personRoles[0]"/>
    
    <fr:layout name="tabular">
        <fr:property name="classes" value="style1"/>
        <fr:property name="columnClasses" value="listClasses,,"/>
    </fr:layout>
</fr:create>

Result

Note: this last example probably won't work either because the Person role may not be included or be in the right order.

Top Next: The third situation: input on steroids