Model View Controller – Structuring the server-side

Overview

The MVC (Model View Controller) concept separates the logic behind some user dialog into three parts:

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 page beans belong to the View!

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.

Principal strategies

The principal strategies when developing must be:

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:

Interface facade

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.

Data access facade and Controller per page bean

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>

 

Adding generic thinking...

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.

Project “demosmvc”

Take this project as starting point for own implementations and/or discussions. Project information is available at http://www.CaptainCasa.com/demosmvc/demosmvc.zip

Structure

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”:

Example

How it looks like

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:

The “Model”-part

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()

    {

        ...

    }

}

 

The “Controller”-part

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:

The “View”-part

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.

Details

“Controller”-Part

The are two base classes for managing the logical functions around one 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:

“View”-Part

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.