%@page import="java.io.InputStream"%>
<%@page import="java.io.InputStreamReader"%>
<%@page import="java.io.BufferedReader"%>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
Schemas represent a certain view over an certain type of objects. The schema contains information about which and how each slot should be presented or edited (presented will be used for both situations).
<schema name="person.simple-admin-info" type="net.sourceforge.fenixedu.domain.Person"> <slot name="name"/> <slot name="username"/> <slot name="email"/> </schema>
Each schema must by assigned a unique name. Schemas are referred by name from several locations so they must be easily identifiable. Additionaly you also need to specify the name of the target type. Several functionalities in the schema depend on the type of the schema.
The example shows the simples way of using a schema. A set of slots is declared meaning that
whenever the schema is presented only those slots should be visible. No matter were each
slot will be presented, the presentation will obey the slot's declared order. This means
that, according to the example, the slot name
will appear before the slot
username
and this one will appear before email
. When editing a single
object this specifies, for example, the order of the rows and when editing several objects
this chooses the order of the columns.
When you want to present an object you choose the schema, layout, properties of that
layout. The schema reffers the several slots that should be presented so it's possible
to also specify the same elements for each slot. To do that you msut use the slot's
attributes layout
and schema
, and specify several properties
for the slot.
<schema name="person.simple-admin-info.extended" type="net.sourceforge.fenixedu.domain.Person"> <slot name="name"/> <slot name="username" layout="link"/> <slot name="email"> <property name="link" value="true"/> </slot> <slot name="nationality" schema="country.short" layout="values"> <property name="htmlSeparator" value=" - "/> </slot> </schema>
In this example the email
and nationality
slots have their presentation customized.
In the email
slot we are just passing properties to the renderer associated with
the default layout for slot's type. Nevertheless, in the nationality
slot, we are
specifying both the schema
and attributes
and passing a property to
the renderer associated with the layout values
.
Each slot has a label that can be shown next to the value or value's editor. This label is
the slot's name or a resource message if the key is defined in the module's default bundle.
The conventions used to determine the slot's label are explained in the
You can also override the used convetion and directly specify a key
and
bundle
for each slot. This will be used to obtain the label for the slot.
The schema also has an attribute bundle
. This attributes avoids repeating
the same bundle in all the slot's. So
<schema ... bundle="BUNDLE"> ...
is the same as
<schema ...> <slot ... bundle="BUNDLE"/> <slot ... bundle="BUNDLE"/> ... </schema>
NOTE: You can specify the bundle for the whole schema and then override the bundle for a particular slot.
Slot's can be marked read only. This is only significant when the schema is being used in an input context. If the object is only being presented then this information is obviously ignored. By default all slot's are writable so you normally you only specify slot's that should not be writeable.
<slot ... read-only="true"/>
The input validation is done by validators. Validators are Java classes that extend
net.sourceforge.fenixedu.renderers.validators.HtmlValidator
. To validate
a slot you need to specify the class of the validator that will be used.
<slot ... validator="net.sourceforge.fenixedu.presentationTier..."/>
Since validators are supposed to be generic and reusable thy can be configured with the standard properties system. To pass properties to a validator you need to specify the validator as a inner tag.
<slot ...> <validator class="net.sourceforge.fenixedu.renderers.validators.RegexpValidator"/> <property name="regexp" value="\p{Digit}+"/> </validator> ... </slot>
Sometimes you may need to provide a custom converter for the user input. For example if
you are accepting an Date
but want to convert the user input to an
sql.Date
or any other subtype. Converters are also Java classes so you also need
to specify the specific converter to use.
<slot ... converter="net.sourceforge.fenixedu.presentationTier..."/>
Converters should only depend on the type and user input so it's not possible to pass
properties to those converters. For example, associating an sql.Date
converter to a Date
field should understand the user input for that field,
possibly reuse the conversion already available for the Date
type
and then create a new sql.Date
.
Suppose you define a schema to show the personal details of a person. It could contain about 20 well configured and highly tailored slot definitions. Now if you want, in other context, to show the personal information and the information about the person's filiation you are in trouble. You need a new schema but how do you reuse the last schema?
Well, if you were thinking Copy&Paste that always works but is not what I want to say. If you simply want to present the information you can create a new schema with the slots for the filiation's information an present the person two times: the first with the first schema and the second with the schema you just created. Off course you will have all the drawbacs of having two presentations: You can't really mantain an uniform presentation of all the information together and you can't use the same strategy to edit that information.
You can reuse previous schemas by extending them. The extension mechanism is in some (and few) ways similar to the extension in OO languages. Let's use an example to show how things work:
<schema name="person.personal" type="net.sourceforge.fenixedu.domain.Person"> <slot name="name"/> <slot name="birthdate"/> <slot name="email"/> </schema> <schema name="person.filiation" type="net.sourceforge.fenixedu.domain.Person" extends="person.personal"> <remove name="email"/> <slot name="nameOfFather"/> <slot name="nameOfMother"/> </schema> <schema name="person.filiation.edit" type="net.sourceforge.fenixedu.domain.Person" extends="person.filiation"> <slot name="name" read-only="true"/> <slot name="birthdate" read-only="true"/> <slot name="email" read-only="true"/> </schema>
In this example the person.filiation
schema is extending the person.personal
schema. Additionally we are removing the email
slot in the person.filiation
schema. This means that the schema will have 4 slots in the order name
, birthdate
,
nameOfFather
, and nameOfMother
.
The remove tag indicates slots that you don't want to include/inherit. This is preformed before any other
thing is considered in the schema that is extending, in this case person.filiation
.
You can also "override" slot's definitions. If you declare a slot with a name that exists in the
parent then you are overriding it. This means that the slot will remain in the position it was first declared
but will only have the properties of it's last declaration. In the last schema, named person.filiation.edit
,
we are extending the person.filiation
schema and indicating that the slot's included/inhertited
from the person.personal
schema are read only. Nevertheless this schema contains all the
slot's in the same order as person.filiation
. All the new slot's are included after existing
slot's.
A note about problems of reuse. Schemas are intended to be reused in several presentation through out the application. So, if you change a schema check the schemas that are extending that one. If your changes are too big you can choose to make a new schema or change the name and check wich dependencies fail (schemas can only extend existing schemas and an errors will be reported).
You can also refine a previously define schema by using declaring something like:
<schema name="person.filiation.refined" type="net.sourceforge.fenixedu.domain.Person" refines="person.personal"> <remove name="email"/> <slot name="nameOfFather"/> <slot name="nameOfMother"/> </schema>
This example is similar to the previously. The difference is that the schema person.personal
is also changed. In the end the schema being defined and person.personal
will be equal, that
is, will contain the same slots and the same definitions.
This technique can be usefull to separate changes made to a schema from the initial version of the schema will keeping the most up-to-date definition. Nevertheless this technique should be used with care since it's no longer be obvious wich slot's are included in a schema when it's used.
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:
(...) <slot name="username" default="pxxxxx" (...) <slot name="idDocumentType" default="IDENTITY_CARD" (...) <slot name="maritalStatus" default="UNKNOWN" (...) (...)
Default values 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.
Check the
Normally the renderers architecture use the standard Java Beans notation. Objects that need to be created are created using the default constructor and each slot's value is obtained with the standard getter and altered with the standard setter.
Nevertheless when we need to ensure some consistency in the changed or created objects we need to invoke more complex constructors or invoke setters that take multiple arguments and do verifications that can only be done correctly if all the values are available at the same time.
Schemas were extended to allow you to specify that information when needed. For each schema you can indicate wich constructor is called instead of the default construtor and wich special setters replace the standard setters. Here is an generic example:
<schema ... constructor="s1,s3:java.sql.Date,s2:Integer"> <slot name="s1"/> <slot name="s2"/> <slot name="s3"/> <slot name="s4"/> <slot name="s5"/> <slot name="s6"/> <setter signature="setSomething(s4,s5)"/> </schema>
This example is generic to exercise all the possibilities of this mechanism. The constructor
for the object is specified as the constructor
attribute of the schema. You can
only specify one constructor for that schema. Special setters are declared using the
setter
inner tag. You can declare multiple setters. For both the constructor
and setter you specify a signature. The main difference between them is that you don't (can't)
specify the name in the constructor. Nevertheless the way that signature identifies the
correct method/constructor is the same.
Each signature consists of a name (as said only valid in the setter signature) and a list of
parameters separated by commans. Each parameter consists of slot name and can be accompanied
by a type declaration. If the type is not given then it's assumed to be the type of the slot.
The type can be given to force the parameter type to a subclass of the slots type. For instance
if slot s3
is of type java.lang.Date
then, in the example, we are
forcing the parameter to the subtype java.sql.Date
.
Explaning the example, we can see that whever we use this schema to create an object then the object will be created using a constructor that has 3 parameters with the types
s2
java.sql.Date
java.lang.Integer
and passing as arguments the values of the slot s1
, s3
, and s2
respectively. After creating the object the slots used in the constructor are considered seted
so we are left with the slots s4
to s6
to set. The slots
s4
and s5
are referred in a special setter. so this setter is used.
This means that after creating the object the setter setSomething
is called
passing as arguments the values of the slots s4
and s5
. This slots
are considered seted so the only slot left is s6
. This slot was not used in
the constructor neither was he used in a special setter so it will be seted using the standard
setter setS6(s6)
.
In the constructor and setters signature you can only reffer slots defined in the schema. The problem is that in most cases values that you need to specify in the constructor, for example, are context dependant, that is, they are passed as hidden slots from the JSP.
To overcome this problem you can declare those slots also in the schema and mark them as
hidden with the hidden
atrribute. These slots will not be considered in the
presentation but will be available to the constructor and setter declaration. Off course that
if no hidden slots is really passed in the JSP the constructor will be passed the null value
for that slot. See more in the
<slot ... hidden="true"/>
<% String dtdLocation = "/WEB-INF/schemas/fenix-renderers-schemas.dtd"; InputStream stream = pageContext.getServletContext().getResourceAsStream(dtdLocation); if (stream == null) { out.write("Could not find DTD in " + dtdLocation); } else { InputStreamReader reader = new InputStreamReader(stream); BufferedReader buffered = new BufferedReader(reader); String line = null; while ((line = buffered.readLine()) != null) { out.write( line.replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll("\"", """) + "\n"); } } %>