The MVC (Model View Controller) concept separates the logic behind some user dialog into three parts:
The “View”-part is the one that is directly acting with user interface and that is responsible for building up and arranging controls – and receiving events from the user interaction.
The “Controller”-part is the one that contains the logical processing.
The “Model”-part is the one the grants access to the data.
The MVC concept tries to separate as much “real” logic as possible from the “View”-part, in order to keep a high flexibility (e.g. when exchanging UI elements or even the whole UI framework) and in order to structure the software in a solid way.
This implies, that the “View”-part is the only one to hold direct dependencies to the UI-framework that is used. Any import of libraries of the UI-framework within the “Controller”- or the “Model”-part is strictly forbidden!
CaptainCasa provides a server side UI processing. This quite often makes people think that the “View”-part is on the client - and as consequence the page beans must be something like the “Controller”-part...
This is not true at all: page beans are server-side representatives of their client side UI representation! - Actually it's the “View”-part which is split in CaptainCasa between the client side “View”-rendering and the server side “View”-processing.
Consequence: page beans are part of the “View” - which also means: they are the ones to use all the lovely server-side classes of CaptainCasa.
The principal strategies when developing must be:
Any decision and processing which is not directly related to the UI must be outsourced from the “View”-part to some “non-View”-part.
Example: a combo box requires valid values for currencies. The way to find out the valid values (e.g. by looking up a database table) is not relevant at all on “View”-level. Consequence: you simply outsource this to some interface, which then has to take care – instead of doing any implementation on “View”-layer.
There are two common ways to do this:
Implementing a facade
Implementing a data access facade and a controller per page bean
You may create an interface “ILogic” which serves as facade to all functions below the UI – and you add some implementation which may be a dummy implementation at the beginning and which is exchanged by some real implementation during the project.
Example:
public interface ILogic
{
...
public List<String> getCurrencies();
public BigDecimal calculateIntoCurrency(String from,
String to,
BigDescimal amount,
LocalDate at);
...
}
Maybe at a later point of time you may want to make things more generic, e.g. when working with Pojo-instances. So the interface might change into the following...
...
public List<IdWithText> getValuesForBeanProperty(Object bean,
propertyName);
...
...so that have generic UI implementations.
The advantage of the Interface facade: it is simple to use especially for stateless functions and it is cleaning up your UI code from the beginning on.
The disadvantage: it is sometimes not trivial to use with stateful functions, so the view layer typically is the only one to keep conversational state. And: the number of functions within the interface gets very high.
As consequence you typically continue to separate concerns a bit better – as shown in the next chapter.
The model for separating the different concerns in a better way is:
All functions for data access are arranged in a facade interface(s):
public class Person()
{
String id;
String firstName;
set/getId(...)
set/getFirstName(...)
}
public interface IDataAccessPerson
{
public List<Person> readAllPersons();
public Person readPerson(String id);
...
}
The interface is available both to the “View”-part and to the “Controller”-part...
Now imagine a dialog in which a person is edited. All the logical processing is outsourced to the controller part:
public class PersonEditController
{
Person m_person;
public PersonEditController(Person p)
{
m_person = p;
}
public Result validate() {...}
public Result savePerson() {...}
public void applyDefaults() {...}
...
}
An instance of this controller is now plugged to the UI processing – in CaptainCasa this is the page bean:
public class PersonEditUI extends PageBean
{
Person m_person;
PersonEditoController m_controller;
public void prepare(Srting personId, ...)
{
m_person = DataAccessPerson.instance().readPerson(personId);
m_controller = new PersonEditController(m_person);
}
public Person getPerson() { return m_person; }
public void onCheckAction(ActionEvent event)
{
Result r = m_controller.validate();
if (r.hasError())
Statusbar.outputError(“Problem :” + r.getErrorText());
}
public void onSaveAction(ActionEvent event)
{
Result r = m_controller.save();
if (r.hasError())
Statusbar.outputError(“Problem :” + r.getErrorText());
}
}
The layout in CaptainCasa may be something like:
<t:rowheader>
...
<t:button text=”Check” actionListener=”#{d.PersonEditorUI.onCheckAction}”/>
<t:button text=”Save” actionListener=”#{d.PersonEditorUI.onSaveAction}”/>
...
<t:rowheader>
<t:rowbodypane>
...
...
<t:row>
<t:label text=”First name” with=”100”/>
<t:field text=”#{d.PersonEditUI.person.firstName}” width=”100%”/>
</t:row>
<t:row>
<t:label text=”Last name” with=”100”/>
<t:field text=”#{d.PersonEditUI.person.lastName}” width=”100%”/>
</t:row>
...
...
</t:rowbodypane>
Following the principal strategies it may make more and more sense to build up generic frameworks on top.
A typical area where it definitely makes sense is the area of form input, in which a certain object (e.g. a Pojo from the data layer) is edited within some form. Properties of the object are represented by corresponding controls (field, combo box, checkbox, radio button, ...) - validation errors need to be reflected into the corresponding controls, so that the user see where data is incorrect.
The demo project “eclnt_demosmvc” is exactly showing how to build up such framework.
Take this project as starting point for own implementations and/or discussions. Project information is available at http://www.CaptainCasa.com/demosmvc/demosmvc.zip
When viewing the package structure then you see than both framework classes and demo-application classes are kept in three package “model”, “view” and “controller”:
The following dialog (with red lines around) is the edit screen for a Person-object:
The screen is uses in the context of a list processing on the left.
There are certain functions:
When validating then the properties that cause errors are marked with a red icon on the top right.
Combo box input is bound to the application logic.
The data is kept in a Person-object:
package demo.model;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclnt.util.valuemgmt.UniqueIdCreator;
@XmlRootElement
public class Person implements Cloneable
{
String m_id = UniqueIdCreator.createUUID();
int m_gender; // 0=male, 1=female, 2=diverse
String m_firstName;
String m_lastName;
String m_title;
String m_department;
@Override
public Person clone()
{
try
{
return (Person)super.clone();
}
catch (Throwable t) { throw new Error("Problem when cloning",t); }
}
public String getId() { return m_id; }
public void setId(String id) { m_id = id; }
public String getFirstName() { return m_firstName; }
public void setFirstName(String firstName) { m_firstName = firstName; }
public String getLastName() { return m_lastName; }
public void setLastName(String lastName) { m_lastName = lastName; }
public String getTitle() { return m_title; }
public void setTitle(String title) { m_title = title; }
public String getDepartment() { return m_department; }
public void setDepartment(String department) { m_department = department; }
public int getGender() { return m_gender; }
public void setGender(int gender) { m_gender = gender; }
}
Why the annotation “@XMLRootElement”? Because the object is persisted as XML-string in the file system using JAXB...
And there is access to Person-instances:
package demo.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.eclnt.demosmvc.framework.util.JAXBUtil;
import org.eclnt.jsfserver.streamstore.IStreamStore;
import org.eclnt.jsfserver.streamstore.StreamStore;
public class PersonDataAccess
{
public static Person createPerson()
{
return new Person();
}
public static Person readPerson(String id)
{
...
}
public static void savePerson(Person p)
{
...
}
public static List<Person> queryPersons()
{
...
}
}
package demo.controller;
import java.util.ArrayList;
import java.util.List;
import org.eclnt.demosmvc.framework.controller.BeanController;
import org.eclnt.demosmvc.framework.controller.BeanPropertyController;
import org.eclnt.demosmvc.framework.controller.IValidValuesFinder;
import org.eclnt.demosmvc.framework.util.IdText;
import org.eclnt.demosmvc.framework.util.Result;
import demo.model.Person;
import demo.model.PersonDataAccess;
public class PersonController extends BeanController<Person>
{
public PersonController(Person bean)
{
super(bean);
}
@Override
protected void configure(Person bean)
{
{
BeanPropertyController<Person> bpc = new BeanPropertyController<Person>(bean,"firstName");
bpc.setMaxLength(50);
bpc.setMandatory(true);
addPropertyController(bpc);
}
{
BeanPropertyController<Person> bpc = new BeanPropertyController<Person>(bean,"lastName");
bpc.setMaxLength(50);
bpc.setMandatory(true);
addPropertyController(bpc);
}
{
BeanPropertyController<Person> bpc = new BeanPropertyController<Person>(bean,"title");
bpc.setMaxLength(20);
bpc.setMandatory(false);
addPropertyController(bpc);
}
{
BeanPropertyController<Person> bpc = new BeanPropertyController<Person>(bean,"department");
bpc.setMaxLength(50);
bpc.setMandatory(true);
bpc.setValidValuesFinder(new IValidValuesFinder<Person>()
{
@Override
public List<IdText> findValidValues(Person bean)
{
List<IdText> vvs = new ArrayList<IdText>();
for (int i=0; i<25; i++)
vvs.add(new IdText(""+i,"Department " + i));
return vvs;
}
@Override
public String findTextForId(String id)
{
return "Department " + id;
}
});
addPropertyController(bpc);
}
{
BeanPropertyController<Person> bpc = new BeanPropertyController<Person>(bean,"gender");
bpc.setMandatory(true);
bpc.setValidValuesFinder(new IValidValuesFinder<Person>()
{
@Override
public List<IdText> findValidValues(Person bean)
{
List<IdText> vvs = new ArrayList<IdText>();
vvs.add(new IdText("0","Male"));
vvs.add(new IdText("1","Female"));
vvs.add(new IdText("2","Diverse"));
return vvs;
}
@Override
public String findTextForId(String id)
{
if ("0".equals(id)) return "Male";
if ("1".equals(id)) return "Female";
if ("2".equals(id)) return "Divers";
return null;
}
});
addPropertyController(bpc);
}
}
@Override
protected void validateBean(Result r)
{
// some special logic that is hard coded here
Person p = getBean();
if (p.getFirstName() != null)
{
if (p.getFirstName().equals("Martin") && p.getGender() == 1)
{
r.addError("First name does not match gender",
"firstName","gender");
}
}
}
@Override
protected void saveExecute(Result r)
{
PersonDataAccess.savePerson(getBean());
}
}
There are two important issues:
Per object property a “property controller” is defined – in which all logic around the property is arranged. Please note that the property controller for the “department” and for the “gender” are quite similar (providing list of valid objects) – whereas their graphical representation on dialog level is quite different: the “department” is edited as a combo box and the “gender” is edited by a radio button group.
When processing (e.g. validating) there is not only a “stupid” boolean result returned back, but a more comprehensive Result object. In the “validateBean”-method you see that the error information always relates to properties.
public class PersonUI extends BeanUI<Person>
{
// ------------------------------------------------------------------------
// inner classes
// ------------------------------------------------------------------------
public interface IListener extends BeanUI.IListener
{
public void reactOnCancel();
}
// ------------------------------------------------------------------------
// members
// ------------------------------------------------------------------------
private IListener m_listener;
// ------------------------------------------------------------------------
// constructors
// ------------------------------------------------------------------------
public PersonUI()
{
}
@Override
public String getPageName() { return "/demosmvc/demos/person.jsp"; }
@Override
public String getRootExpressionUsedInPage() { return "#{d.PersonUI}"; }
// ------------------------------------------------------------------------
// public usage
// ------------------------------------------------------------------------
public void onCancelAction(ActionEvent action)
{
if (m_listener != null) m_listener.reactOnCancel();
}
// ------------------------------------------------------------------------
// private usage
// ------------------------------------------------------------------------
public void prepare(BeanController<Person> beanController,
IListener listener)
{
super.prepare(beanController, listener);
m_listener = listener;
}
@Override
protected void configure(Person bean)
{
addAdapter(new AdapterFieldString<Person>(getBeanController().getPropertyController("firstName")));
addAdapter(new AdapterFieldString<Person>(getBeanController().getPropertyController("lastName")));
addAdapter(new AdapterFieldString<Person>(getBeanController().getPropertyController("title")));
addAdapter(new AdapterFieldString<Person>(getBeanController().getPropertyController("comment")));
addAdapter(new AdapterComboFieldString(getBeanController().getPropertyController("department")));
addAdapter(new AdapterRadioButtonInt<Person>(getBeanController().getPropertyController("gender")));
}
}
In the view part's “configure”-method the property controls are created. These are instances of component adapters (which are part of the demo project).
Please also note: there are events from the user interface e.g. “Validate” that are directly passed to the controller (by the super-class) and there are other events e.g. “Cancel” that are handled on UI level, without requiring to inform the controller.
Finally the XML layout...
<t:beanprocessing id="g_1" beanbinding="#{d.PersonUI}" />
<t:rowbodypane id="g_2" rowdistance="5"
stylevariant="cc_directcontent">
<t:row id="g_3">
<t:field id="g_4" adapterbinding="#{d.PersonUI.adapters.title}"
width="200" />
</t:row>
<t:row id="g_5" coldistance="10">
<t:field id="g_6" adapterbinding="#{d.PersonUI.adapters.firstName}"
width="100%" />
<t:field id="g_7" adapterbinding="#{d.PersonUI.adapters.lastName}"
width="100%" />
</t:row>
<t:row id="g_8">
<t:radiobutton id="g_9"
adapterbinding="#{d.PersonUI.adapters.gender}" group="GENDER"
refvalue="0" text="Male" />
<t:radiobutton id="g_10"
adapterbinding="#{d.PersonUI.adapters.gender}" group="GENDER"
refvalue="1" text="Female" />
<t:radiobutton id="g_11"
adapterbinding="#{d.PersonUI.adapters.gender}" group="GENDER"
refvalue="2" text="Diverse" />
</t:row>
<t:row id="g_12">
<t:combofield id="g_13"
adapterbinding="#{d.PersonUI.adapters.department}" width="50%" />
</t:row>
<t:row id="g_14">
<t:textarea id="g_15"
adapterbinding="#{d.PersonUI.adapters.comment}" height="100"
width="100%" />
</t:row>
</t:rowbodypane>
<t:rowheader id="g_16" coldistance="5">
<t:coldistance id="g_17" width="100%" />
<t:button id="g_18" actionListener="#{d.PersonUI.onValidateAction}"
text="Prüfen" width="100+" />
<t:button id="g_19" actionListener="#{d.PersonUI.onSaveAction}"
text="Speichern" width="100+" />
<t:coldistance id="g_20" width="20" />
<t:button id="g_21" actionListener="#{d.PersonUI.onCancelAction}"
text="Abbrechen" width="100+" />
</t:rowheader>
...in which you see the arrangement of the page bean components.
The are two base classes for managing the logical functions around one bean:
“BeanController” is the one that manages the whole bean
“BeanPropertyController” is the one the manages one property of the bean
The “BeanController” class provides a “configure”-method, in which extensions of “BeanController” can initialize. The main part of initialization is the set up of “BeanPropertyController” instances.
When calling the validation of a “BeanController” instance, then firs the property controller instances are validated before own validations are executed.
The “BeanPropertyController” instances provide the information for the controls in a way that is independent from the view concept:
Example: it provides a list of valid values without knowing which graphical representation is selected (in the example: combo box or radio button group=
Example: it just provides the information enabled/disabled – without knowing what the impact of this is on background/foreground coloring
The “View”-Part consists out of the base-page bean for inherit from (“BeanUI”). This one contains a map of adpater bindings per component that provide the attribute implementations for the corresponding property.
The base-page is bound to its “BeanController” instance, the page bean components are bound to their “BeanPropertyController” instance.
The adapters are residing in package “...view.controls”, all inheriting from “AdapterBase”.
Please check the Developer's Guide for more information on component adapter binding.