About this Guide

This guide tells you how to develop own screens with CaptainCasa Enterprise Client RISC.

Please note that there are a couple of examples that are delivered with the Enterprise Client Installation. Open the “Demo Workplace” to view the examples. For every control there is at least one example showing how to use it. The demo web application contains all the Java source files (“workplacesrc” directory) and all the JSP files (“workplace” directory)

For this reason this guide will only summarize the principles of working with certain components – the details are contained in the examples.

Before reading this guide it is useful to read the “Tutorial - First Development Steps with CaptainCasa Enterprise Client”. The tutorial explains step by step how to create the first project and the first screens.

Copyright Notice

All intellectual property that is contained in CaptainCasa Enterprise Client belongs to CaptainCasa GmbH. You must not copy or decompile any part of CaptainCasa Enterprise Client without the explicit agreement of CaptainCasa GmbH. Certain CaptainCasa license types grant the “full access” to the sources – please contact info@CaptainCasa.com for more information.

A usage of CaptainCasa Enterprise Client in a productive environment and a redistribution is only allowed with obtaining an official license from CaptainCasa GmbH. Various types of licenses are available, including the so called “binary license” which allows the free usage and free redistribution as part of your application. Contact info@CaptainCasa.com for obtaining detailed license information.

The “binary license” of CaptainCasa Enterprise Client is provided WITHOUT WARRANTIES.

CaptainCasa Enterprise Client makes use of the following contained libraries / software products:

Project Setup

Please read the documentation “Tutorial - First Development Steps with CaptainCasa Enterprise Client” in order to step by step create a CaptainCasa project and to learn how to execute first development steps.

Developing with CaptainCasa means that you set up projects in which you manage layout definitions, resources (images) and your server side code (Java). When creating a project within the CaptainCasa toolset there are various options – this chapter explains the structural aspects behind.

Basics

Different type of artifacts

The server side of CaptainCasa Enterprise Client is based on the default JEE web application concept: somewhere you have a directory which represents the web content. The directory structure of this web content looks like this:

/<web application>

  /...

  /images

    icon1.png

  /META-INF

  /WEB-INF

    /lib

      *.jar

    /classes

      Page1UI.class

      Page2UI.class

    web.xml

  page1.xml (or page1.jsp)

  page2.xml (or page2.jsp)

 

The structure is defined by the JEE-Servlet specification. It is used for building so called .war files, which are the ones to be deployed into various application server environments.

Inside the web application you provide the following artifacts:

You see: the directory structure which is used at runtime by the servlet container (e.g. Tomcat) contains various files: some authored by you (your jsp-pages, your code), some coming from CaptainCasa and some coming from other parties.

The CaptainCasa toolset together with any development environment takes care of authoring your artifacts and deploying them into a directory structure that is the one shown above.

Name of layout definitions: .jsp and .xml

At the beginning (the CaptainCasa framework was started 2007) there was a closer binding of the CaptainCasa server-side processing to JSF than in the meantime. This is where the extension “.jsp” for layout definitions originates from. We updated to the extension “.xml” in the meantime, so now you can work with both extensions. The important issue: the XML-layout-content of both files are the same – it's just the extension which differs...

Project Structure

The project directory structure on the one hand is somehow related to the deployment directory structure – but of course on the other hand differs. While the purpose of the deployment directory structure is to “melt everything together” in order to efficiently deploy, the purpose of the project directory structure is to clearly separate your own artifacts (the ones to check-in/out to/from a version control system), the compiled results of your artifacts and the artifacts from other parties.

When creating a project by using the CaptainCasa toolset there is a configuration dialog at the very beginning, in which you define the project layout:




The important parameters are:

Default Project (no “compact”, no “hot deployment”)

When creating a project within the CaptainCasa toolset and doing nothing else than specifying the project's name and the project's directory then the project structure looks as follows:

<project directory>

  /src

  /webcontent

  /webcontentbuild

  /wencontentcc

 

There are two directories which are the ones to be used during development by yourself:

The “/webcontent” directory internally is structured in the same way as the deployed web application is structured:

<project directory>

  ...                                        

  /webcontent

    /images

       xyz.png

    /WEB-INF                                 

      web.xml

      faces-config.xml

    xyz.jsp

  ...

 

There is one directory which is storing the compiled classes: “/webcontentbuild”. When using Eclipse then automatically all Java code below “/src” is compiled into “/webcontentbuild/WEB-INF/classes”:

<project directory>

  /src

    XYZ.java                 ---- Compile ----

  ...                                        |

  /webcontentbuild                           |

    /WEB-INF                                 |

      /classes                               |

        XYZ.class            <----------------

  ...

 

And there is one directory “/webcontentcc” which contains all these files which are added by CaptainCasa into the web content, so that it contains the JSF libraries, the CaptainCasa library, default dialogs (e.g. JSP pages for OK- and Yes-No-dialogs), etc.

<project directory>

  ...                                        

  /webcontentcc

    /eclnt <== client side libraries

    /eclntjsfserver <== server side default dialogs, styles, etc.

    /WEB-INF

      /lib

        eclnt_jsfserver.jar <== CaptainCasa library

        jsf-api.jar <== JSF

        jsf-impl.jar <== JSF default implementation

  ...

 

Using this directory structure the deployment into the servlet container that is executed by the CaptainCasa toolset is quite simple:

All three directories that hold webcontent (webcontent, webcontentbuild, wencontentcc) are copied and merged into the deployment directory. In case of using Tomcat the scenarios looks as follows:

<project directory>                <tomcat directory>

  /src                               /webapps

  /webcontent       -------copy-----> <project webapp>

  /webcontentbuild ----|

  /webcontentcc --------|

 

Why all this effort? And why having three different webcontent-dirctories?

The answer: because now own artifacts that are properly separated into:

There is no mix of files. When it comes to e.g. sharing the sources within a repository (e.g. subversion, CVS), the n the decision what to share and what not is quite simple:

<project directory>

  /src                  <== Share!

  /webcontent           <== Share!

  /webcontentbuild      <== Do not share!

  /wencontentcc         <== Share? ...your choice!

 

Clearly, the “src” and the “webcontent” folders are the one that you must share. And clearly the “webcontentbuild” folder must not be shared, because it contains compiled files.

Within “/webcontentcc” the CaptainCasa environment is kept. This files are copied into this directory when creating the project and – later on – when updating the project to a new CaptainCasa version. When it comes to the question if to share these files within a repository, then there are two strategies:

Hot Deployment Configuration

Every time you change Java sources in the project you need to transfer the compiled classes to the Tomcat instance in which you want to test. This on the one hand means that the corresponding classes need to be copied from your project into the Tomcat directory – and the other hand means that Tomcat must somehow restart in order to run the new classes.

Copying and restarting is managed by the CaptainCasa toolset: by default (we call it “reload”) a management-API of Tomcat is accessed that allows to restart a dedicated web-application inside Tomcat. But: this “reload” may still take some significant amount of time because restarting an application may come with a lot of time spent on initializing your application.

Hot deployment provides some efficient mechanism to overcome this. UI related classes typically are changed quite frequently during UI development – while logic-related classes are not changed as frequently. Hot deployment separated the UI classes into some own classloader which runs below the normal classloader of the web-application. As consequence this UI-classloader can be exchanged without restarting the whole web-application.

From Java project perspective there is no difference between a “normal” project and a “hot deployment” project. The hot deployment configuration is used within the CaptainCasa toolset when copying/deploying the project content into the Tomcat-run-time: certain classes are not copied into the WEB-INF/classes folder of the web application, but are copied into the eclnthotdeploy/classes folder – which is the directory accessed by the UI-classloader.

When switching on hot deployment during project creation then all classes that you develop are treated as “hot deployment classes”. You can define this more fine granular on a package base later on.

Other Project Structure

All the project structures that you have read about so far, are not implemented in a hardwired way within the CaptainCasa toolset. Per project there is an XML configuration file that explicitly defines where the toolset can find what information – in order to properly access files (e.g. JSP-pages) and in order to correctly deploy.

As consequence you can setup any project structure on your own and guide the CaptainCasa toolset what to do using the project configuration file.

The structure of this file is explained in the following text.

Maven Project Structure

There is an own documentation that explains how to set up CaptainCasa projects that are managed using Maven. CaptainCasa provides corresponding Maven project archetypes that make the creation of Maven projects very simple.

Maven Spring Boot Project Structure

There is an own documentation that explains how to set up CaptainCasa projects that are managed using Maven and that are using Spring Boot as framwork and runtime. CaptainCasa provides a corresponding Maven project archetype that makes the creation of this type of project very simple.

Project file of the Enterprise Client Toolset

Basics

The project file is generated automatically when you create the project using the Enterprise Client toolset. It is stored as file “.ccproject” inside your project root directory.

Project file in project folder:

 

<projectdirectory>

  .ccproject ==> contains project configuration

 

 

Link to project file in CaptainCasa toolset:

 

<CaptainCasa Install Directory>

  /server

    /tomcattools

      /webapps

        /editor

          /config

            ...

            <project>.xml ==> contains link to <projectdirectory>

            ...

 

Inside the project file, there are a couple of parameters for defining where the project files are located.

Most significant Attributes

The “ccfirst” project that was created within the tutorial has the following content:

<project

    projectdirectory   ="c:\projects\ccfirst"

    webcontentdirectory="${project}/webcontent"

    javasourcedirectory="${project}/src"

    javasourcewebinfdirectory="${project}/src_webinf"

    

    javaclassdirectory="${project}/webcontentbuild/eclnthotdeploy/classes"

    javaclasswebinfdirectory="${project}/webcontentbuild/WEB-INF/classes"

    

    webappaddonsdirectory="${project}/webcontentcc"

    

    webcontentdeploydirectory="C:/temp/cc20130909/server/tomcat/webapps/ccfirst"

    webcontextroot           ="ccfirst"

    webhostport              ="localhost:50000"

    

    ...

    ...

    >

 

 

    <deploycopyinfo fromdir="${project}/webcontentbuild"

                    todir="${projectdeploy}"/>

    <deploycopyinfo fromdir="${project}/webcontentcc"

                    todir="${projectdeploy}"/>

 

    

</project>

 

You already see the most significant attributes:

Other Attributes

There are many other attributes that you may use within the project file. A documentation of these attributes is automatically copied into the comment section of the XML file that is created for your project.

Deployment Configuration

When using the deployment of the Enterprise Client toolset (“Reload server” or “Hot Deploy”) then all the relevant files are transferred from the project directory structure into the deployment directory:

After the file transfer has finished, the toolset triggers the servlet engine (default: Tomcat) to load the new information. In case of a “reload” this means that the web-application is re-started, in case of “hot deploy” this means that parts of the classes are reloaded without re-starting the whole web application.

Referencing special Values via Variables

Within the project configuration you may reference “special values”:

<install>

  server

    tomcat

      webapps

        <location of deployed projects>

    tomcattools

      webapps

        editor

 

Especially when sharing projects via SVN/GIT/... make use of the variables in order to define project structures which are self-containing and which can be easily transferred from one location to the other.

Template Management

When creating a new layout you pick a certain template as base for the .jsp file that is opened within the Layout Editor. By default there are a couple of templates predefined by CaptainCasa:

You can extend the list of templates by adding own ones through the project definition:

<project ... >

  ...

  <template resource="/abc/def.jsp" image="/ghi/jkl.jpg"/>

  ...

  <template ... />

  <template ... />

</project>

 

Each template must provide a JSP file which is copied into the newly created page. And it must provide an image which is displayed within the template selection. Both files need to be located within your web application, so that they are accessible as resource.

Project configuration

Inside your project there are a couple of configurations that you may apply during development.

All these files can be accessed by using the CaptainCasa toolset by pressing the button “Configuration...” below the project selection:

When opening one of the configuration files then a dialog will appear in which the current content of the configuration is shown on the top and a template files is shown at the bottom:

The file that is edited is stored within your project in the following location:

<project>

  /webcontent

    /eclntjsfserver

      /config

        logging.xml

        system.xml

        …

 

The template files are located at the following location within the webcontentcc-part of the project:

<project>

  /webcontent

    /eclntjsfservercc

      /config

        logging.xml_template

        sessiondefaults.xml_template

        system.xml_template

        …

 

When opening the template files you will find a lot of comments within files that document the configuration possibilities.

Logging – Where do I find CaptainCasa logs?

The server side of CaptainCasa logs its internal processing using the default Java logging. The default logging is done into the work directory of the servlet container. For Tomcat this is the “tomcat/work/Catalina/localhost/<yourWebApp>” directory.

Example:

 

/tomcat

  /webapps

    /yourWebApp

  /work

    /Catalina

      /localhost

        /yourWebApp

          log_eclntjsfserver.txt.0

          log_performance.txt.0

 

There are two type of log files:

By default the logging is switched to INFO level, this means every request's processing is logged. We recommend to use the INFO level during development and then when deploying to production scenarios switch to WARNING level.

By default the log is output and stored to 5 files (that's the reason for the numbering behind the “.txt” in the file name), each one having a maximum size of 100MB. If 5 files are written and the last one exceeds a size of 100MB then the oldest file will be overwritten with new log content. (You may update this configuration of course.)

Configuration of logging

You may configure the logging by defining file “<project>/webcontent/eclntjsfserver/config/logging.xml”. There is a template file “logging.xml_template” in the webcontentcc-part of the project.

You either may edit the file directly or you may open the file from the toolset:

The most important information to configure is:

A typical configuration is:

<logging level="INFO"

         console="true">

</logging>

 

The output to the console is quite useful during development. It's much easier to detect stack traces if they are output to the console than if they are output to some log file...

Other logging parameters are described in the “logging.xml_template” file. Please also view the chapter “Appendix - Log Configuration” for more details on how to set up the logging.

Difference “JEE” vs. “Jakarta”

Packages were renamed from “javax.*” to “jakarta.*”

CaptainCasa is using server side standard Java libraries that are part of the Java Enterprise API definitions. Example: the server-side is based on the Servlet-API.

Unfortunately there was a drastic change inside these standard libraries in the year 2020, which was caused by the responsibility for Java Enterprise moving from Oracle into the Eclipse foundation: all package names were renamed from “javax.*” to “jakarta.*”. Example: the class name for the base class for all servlet classes changed in the following way:

Java EE   :    javax.servlet.HttpServlet

Jakarta EE:    jakarta.servlet.HttpServlet

 

This change is a drastic one: in your code you typically have quite some dependencies to classes from the Java Enterprise standard libraries. So moving from the “Java EE” environment into the “Jakarta EE” environment means, you have to update the dependencies correspondingly and you need to change your import-references in your source code.

CaptainCasa delivers two versions of its framework:

Both versions are available with each update and can be downloaded from our download page. It's you to select the right version – dependent on your selection, if you want to stay on the “Java EE” line or if you want to use the “Jakarta EE” line.

This document assumes you are working on the “Java EE” line. If you work on the “Jakarta EE” line then some package names are different. Please check the chapter “Appendix – Using Jakarta EE” for more details about these differences.

Tomcat 7,8,9 vs. Tomcat 10

Please consider that the change from “Java EE” to “Jakarta EE” was done within the Tomcat servlet container with its release 10.

This means: deploying a “Java EE” based web application (.war file) into a Tomcat 10 environment will fail: when the application is started you will the “ClassNotFoundException” messages – because classes within the .war file try to access “javax.*” packages, which are not available anymore.

Principles of Development

In the tutorial “First Development Steps” you already got to know the basic aspects and artifacts when developing with Enterprise Client:

Let's take a closer look into the internal structures. As example we continue to use the address-page that is created within the tutorial:



The Layout

The layout of the page above is:

<t:rowtitlebar id="g_1" text="Address Detail" />

<t:rowheader id="g_2">

    <t:button id="g_3"

             actionListener="#{d.AddressDetailUI.onSaveAction}" text="Save" />

</t:rowheader>

<t:rowbodypane id="g_4" rowdistance="5">

    <t:row id="g_5">

        <t:label id="g_6" text="First Name" width="100" />

        <t:field id="g_7" text="#{d.AddressDetailUI.firstName}" width="150" />

    </t:row>

    <t:row id="g_8">

        <t:label id="g_9" text="Last Name" width="100" />

        <t:field id="g_10" text="#{d.AddressDetailUI.lastName}" width="150" />

    </t:row>

    <t:row id="g_11">

        <t:label id="g_12" text="Street" width="100" />

        <t:field id="g_13" text="#{d.AddressDetailUI.street}" width="150" />

    </t:row>

    <t:row id="g_14">

        <t:label id="g_15" text="Town" width="100" />

        <t:field id="g_16" text="#{d.AddressDetailUI.town}" width="150" />

    </t:row>

</t:rowbodypane>

<t:rowstatusbar id="g_17" />

 

Let's pick some elements to demonstrate what's going on:

<t:field id="g_10" text="#{d.AddressDetailUI.lastName}" width="150" />

 

This is the first field definition. The width of the field is “hard-wired”. The text is referencing to a managed bean, meaning: the value is taken from the bean and it is written back into the bean when the user makes changes.

<t:button id="g_3"

             actionListener="#{d.AddressDetailUI.onSaveAction}" text="Save" />

 

This is the button calling an “onSaveAction” method within the bean.

The Managed Bean

The managed bean is a normal bean definition that is the logical counterpart of the page. By default the bean is derived from the “PageBean” class – which is the base class that you should use for any managed bean implementations. - Of course you can add own general bean implementations below this base class and then use theses ones to extend from.

In this example the code is:

package managedbeans;

 

import java.io.Serializable;

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.defaultscreens.Statusbar;

import org.eclnt.jsfserver.pagebean.PageBean;

 

import javax.faces.event.ActionEvent;

 

@CCGenClass (expressionBase="#{d.AddressDetailUI}")

 

public class AddressDetailUI

    extends PageBean

    implements Serializable

{

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

 

    String m_town;

    String m_street;

    String m_lastName;

    String m_firstName;

    

    // ------------------------------------------------------------------------

    // constructors & initialization

    // ------------------------------------------------------------------------

 

    public AddressDetailUI()

    {

    }

 

    public String getPageName() { return "/addressdetail.jsp"; }

    public String getRootExpressionUsedInPage()

                                { return "#{d.AddressDetailUI}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    public String getTown() { return m_town; }

    public void setTown(String value) { this.m_town = value; }

 

    public String getStreet() { return m_street; }

    public void setStreet(String value) { this.m_street = value; }

 

    public String getLastName() { return m_lastName; }

    public void setLastName(String value) { this.m_lastName = value; }

 

    public String getFirstName() { return m_firstName; }

    public void setFirstName(String value) { this.m_firstName = value; }

 

    public void onSaveAction(ActionEvent event)

    {

        if (m_firstName == null || m_lastName == null)

        {

            Statusbar.outputError("Please define all name fields.");

            return;

        }

        m_town = m_firstName + "/" + m_lastName;

    }

 

}

 

You see:

By default there is one instance of the bean available at runtime – per session! So you do not have to pay attention to the fact that several users are accessing your application simultaneously. (Of course you may want to check if there is some conflict if users work in parallel on the same data – but this is some different issue, where you have to think optimistic or pessimistic locking etc.)

You will later on see, that if the page is re-used, there might be several instances according to your coding: e.g. you might render within one screen a “supplier address” on the left and a “vendor address” on the right. both using the “AddressDetailUI”. In this case you have two separate instances – which are fully under your control.

How Objects are created on Server Side

Resolving of Expressions

Let's get into a bit more detail about how an instance of “AddressDetailUI” is created on server side.

When the page is requested by the client then a request is sent to the server, the XML of the page (.jsp/.xml file) is read and interpreted. During interpretation the expressions within the page are resolved in order to pick the dynamic data which is bound to certain attributes.

An expression contains several fragments – separated by a dot “.”. During resolution the fragments are processed from the left to the right.

The “#{d.” Fragment

Let's take as example the expression “#{d.AddressDetailUI.firstName}”.

The first part of the expression is the “d”. And this part is resolved by the just normal JSF mechanism: JSF takes a look into the faces-config.xml file and finds out what class representation is behind the “d”. JSF creates and registers a new instance of this class – typically using a constructor without any parameters.

The faces-config.xml of your project was created during the project creation and contains the following content:

    <managed-bean>

        <managed-bean-name>d</managed-bean-name>

        <managed-bean-class>managedbeans.Dispatcher</managed-bean-class>

        <managed-bean-scope>session</managed-bean-scope>

    </managed-bean>

 

JSF sees that the scope of the bean is a “session”-scope. This means that the bean will be kept in the session – and will not be removed once the current request is processed.

The Dispatcher class that is referenced also was added to your project when creating the project. Of course you can update the faces-config.xml to your needs, e.g. by moving the Dispatcher-class into a different package then the one proposed!

The “.AddressDetailUI.” Fragment

The Dispatcher is a simple object factory – providing the interface “java.util.Map”.

Whereas the normal resolution of a “.” within an expression follows the corresponding “set/get”-methods of a bean, the resolution of a “.” with a map is calling the “Map.get(..)” and “Map.put(..)” methods.

The dispatcher as consequence is called with “get(“AddressDetailUI”)”. It first checks if an object for this name is already available in its map. If not it creates an instance of AddressDetailUI and add the instance to its map.

The following default assumptions are made:

either:    public AddressDetail() { ... }

or    :    public AddressDetailUI(IDispatcher dispatcher) { ... }

 

The “.firstName}” Fragment

Well, now it's easy: the instance of the class AddressDetailUI is checked for the “getFirstName()” method – and finally the value returned by this object is the one that is passed into the corresponding attribute.

Resolution is done with every Request!

What was described before now is executed every time the client sends a request to the server.

Pay attention not to add any intensive operations into the get-methods that are referenced during expression resolution as consequence. The expression resolution must be something very fast because it is repeated with every request!

Viewing the Page outside the Toolset

You can directly start your page from the Layout Editor:



The page will be opened in a new browser window/tab.

The URL of the page that is shown is built in the following way:


http://<host>:<port>/<project>/<nameofpage>.risc?ccstyle=defaultrisc

 

Example: pageName = test.jsp

URL    : http://localhost:50000/textproject/test.risc?ccstyle=defaultrisc

 

 

If the page is defined in sub-directory of “webcontent” then the URL is built in the following way:


http://<host>:<port>/<project>/<subdir>.<nameofpage>.risc

 

Example: pageName = /pages/test.jsp

URL    : http://localhost:50000/textproject/pages.test.risc?ccstyle=defaultrisc

 

 

So, in case of subdirectories do not use a slash “/” as separator but use a dot “.”.

Dispatcher Concept

Simple usage: beans are read from dispatcher's package

You already got to know the “#{d.”-dispatcher in the text before. The dispatcher serves as factory to find beans, typically using their class name. By default the dispatcher tries to find the bean class in the same package that the dispatcher itself is positioned.

When creating a project then a default dispatcher is automatically generated into the project, being located in package “managedbeans”. Of course you can changes this any time – please do not forget to update the faces-config.xml accordingly.

If an expression “#{d.AddressDetailUI. ...} is to be resolved then the default class arrangement is:

managedbeans                 <== Package

  Dispatcher

  AddressDetailUI

 

ccdispatcherinfo.xml

Of course there are situations in which your project grows and you do not want to arrange all UI classes within one package. In this case you may use a “ccdispatcherinfo.xml” file and tell the dispatcher where to search for classes. The “ccdispatcherinfo.xml” must be positioned in the main package of your application.

ccdispatcherinfo.xml         <== main package (“no” package)

managedbeans                 <== Package containing Dispatcher/ page beans

  Dispatcher

  AddressDetailUI

 

The content of the file is some XML configuration:

<dispatcherinfo>

    ...

    <managedpackage name=”other.package1”/>

    <managedpackage name=”other.package2”/>

    ...

    <managedbean name=”ArticleMaster” class=”com.appl.log.ArticleMasterBean”/>

    <managedbean name=”CustomerMaster” class=”com.appl.sales.CustomerMasterBean”/>

    ...

</dispatcherinfo>

 

You can configure...

The sequence in which the dispatcher operates when resolving a name is:

Combination of ccdispatcherinfo.xml files

If combining several .jar files containing managed beans at runtime, then all “ccdispatcherinfo.xml” instances are combined automatically. The loader reads all occurrences of the file within the loaded environment and will internally built up one “virtual”, big ccdispatcherinfo.xml file which is then used.

Previous versions - “dispatcherinfo.xml”

In previous versions of CaptainCasa a file “dispatcherinfo.xml” had to be placed into the dispatcher's package – having exactly the same content as the “ccdispatcherinfo.xml” file that is described above. This is, of course, still supported. - The disadvantage of this concept was/is, that due to the project-dependent package definition, it was not possible for the CaptainCasa runtime to read all “dispatcher-info” files automatically, so you had to always include all project's “dispatcherinfo.xml” contents into one central “dispatcherinfo.xml”. - Now using the “ccdispatcherinfo.xml” file which is always located at the same position (main package), the runtime can automatically combine all occurrences as described.

Some Details about the default Tomcat Configuration

This chapter is only relevant for readers who are experienced with Tomcat configuration issues.

The installation of Enterprise Client comes with a Tomcat server on its own. The purpose is:

In order to “nicely” support the continuous development as described in the previous chapter (“refresh” button in Layout Editor reloads web application) the following configuration items automatically come with the Tomcat installation:

 

<!-- Default set of monitored resources -->

<!--

<WatchedResource>WEBINF/web.xml</WatchedResource>

-->

 

 

The Tomcat that is part of the Enterprise Client installation is not usable for productive usage – it is optimized for pure development usage!

 

 

Working with Components

Components are the essential graphical elements that you use in order to define layouts. A whole page definition consists of an assembly of components, starting with some basic ones (e.g. “t:rowbodypane”) and ending with some specific ones (“t:field”).

Container – Row – Column Arrangement

The typical arrangement of components is to build up component hierarchies that consist out of container components holding rows and rows having components themselves.

You see: containers contain rows, rows contain columns, columns may be containers as well.

All this, together with the possibility to define widths and heights either in a “pixel mode” or in a “percentage mode”, allows you to build very flexible layouts, that correspond to the size of the screen they are called in.

Conventions

For better knowing the role of a component there are some naming conventions:

Let's look at the name “t:rowbodypane”: it is a row component, i.e. you may put it into an container. On the other side it is a container component as well, this means it itself can contain row components.

It may sometimes help to compare container components with HTML tables (“table”), row components with HTML rows (“tr”) and column components with HTML columns (“td”). The same way you can nest HTML tables into HTML tables you can nest containers into containers with Enterprise Client. ...but, be careful: this is a comparison only.

Examples

Controls in Rows in Container

<t:box id="g_4" rowdistance="5" width="100%">

    <t:row id="g_5">

        <t:label id="g_6" text="Street & Number" width="120" />

        <t:field id="g_7" width="100%" />

        <t:coldistance id="g_8" width="5" />

        <t:field id="g_9" width="50" />

    </t:row>

    <t:row id="g_10">

        <t:label id="g_11" text="City" width="120" />

        <t:field id="g_12" width="100" />

        <t:coldistance id="g_13" width="5" />

        <t:field id="g_14" width="100%" />

    </t:row>

</t:box>

 

The container in this case is a “t:box” which comes with some light background and some padding to its content. Inside the box there are two rows, each one individually filled with components. The content of each row is sized by itself, there are no dependencies between the sizing of the first and the second row.

Nested Containers

<t:box id="g_16" width="100%">

    <t:row id="g_17" coldistance="20">

        <t:pane id="g_18" rowalignmenty="top" rowdistance="5"

            width="100%">

            <t:row id="g_19">

                <t:label id="g_20" text="First name" width="100" />

                <t:field id="g_21" width="100%;100" />

            </t:row>

            <t:row id="g_22">

                <t:label id="g_23" text="Last name" width="100" />

                <t:field id="g_24" width="100%;100" />

            </t:row>

            <t:row id="g_25">

                <t:label id="g_26" text="Street" width="100" />

                <t:field id="g_27" width="100%;100" />

            </t:row>

            <t:row id="g_28">

                <t:label id="g_29" text="City" width="100" />

                <t:field id="g_30" width="100%;100" />

            </t:row>

        </t:pane>

        <t:pane id="g_31" rowdistance="5">

            <t:row id="g_32">

                <t:label id="g_33" text="Date of birth" />

            </t:row>

            <t:row id="g_34">

                <t:calendar id="g_35" />

            </t:row>

        </t:pane>

    </t:row>

</t:box>

 

The “t:box” component opens up a “t:row” which itself holds two “t:pane” container components, each one of them individually filled with some content.

Other Layout Components

While the container-row-column arrangement of components is the most typical one for arranging components within one optical layer, there are other components for dedicated purposes:

Non Graphical Components

There are some components which do not have any visual representation. These components are arranged in the layout within the component “t:beanprocessing”. Examples are:

Rules which apply to all Components

Component Sizing Aspects

The sizing of a component is determined by its WIDTH and its HEIGHT attribute.

There are certain sizing modes which can be used simultaneously:

In addition there are special sizing instructions:

Distances between components

All container components provide a ROWDISATNCE attribute which defines the pixel distance between its contained rows. (As always “pixel” is a synonym for some fix size which depends on scaling of the browser.)

In addition there is a “t:rowdistance” component which you can place as row into the container, which just created some empty space with some defined height.

All row components provide a COLDISTANCE attribute which defines the pixel distance between its contained column components. And there is a “t:coldistance” component which you can place as column in order to create some empty space between column controls.

<t:box id="g_37" width="100%">

    <t:row id="g_38" coldistance="5">

        <t:button id="g_39" text="Left 1" width="100+" />

        <t:button id="g_40" text="Left 2" width="100+" />

        <t:coldistance id="g_41" width="100%" />

        <t:button id="g_42" text="Right" width="100+" />

    </t:row>

</t:box>

Action Listener

Most components provide an “actionListener” attribute. This refers to a method implementation within a managed bean. All the events that are associated with one component are sent to the server through this one action listener method.

Each event for a component is associated with a certain command, which is a string value. The command is the name of the specific event, sometimes the command also comes with parameters. The format of the type string is comparable with a method call, examples are: “flush()”, “drop(value)”.

On server side the action listener is represented by a method, providing an “ActionEvent” as parameter. All events that are triggered by Enterprise Client passing a sub-class of ActionEvent – with the name “BaseActionEvent”. This class has a “getCommand()” method that allows you to get the command string value, and it has a “getParams()” method which allows you to get the parameters, if any passed.

All default commands that are used by Enterprise Client are collected in the interface “IBbaseActionEvent”:

Example: a button has an event “invoke()” when the user presses the button and has an event “drop(value)” when the user drags and drops information onto the button. A proper implementation of the server-side action listener looks like this:

public class XYZ

  implements IBaseActionEvent

{

  public void onButtonEvent(ActionEvent ae)

  {

    BaseActionEvent bae = (BaseActionEvent)ae;

    if (bae.getCommand().

        equals(EVTYPE_INVOKE))

    {

      ...

      ...

    }

    else if (bae.getCommand().

             equals(EVTYPE_DROP))

    {

      ...

      ...

    }

  }

}

 

Please note: the command and parameter values are string values. You need to compare using “.equals”, you may not use “==”!

Flush Management (Component Level)

All input components (e.g. field, check box, radio button, etc.) manipulate a piece of data which is typically referenced to a managed bean property.

The default behavior of the client processing is that data changes are kept in the client and wait for the next significant event to be transferred to the server. A significant event may for example be a button event.

All input components provide for the property “flush”. If this is set to “true” then the data change is treated as a significant event and causes a roundtrip in which all data changed is transferred to the server. In addition, each component may provide an action listener which receives a “flush()”-event.

By using the flush mechanism you can build layouts in which parts of the layout directly respond to the user changing data.

With the FIELD component there is an attribute FLUSHTIMER in addition. By default, when setting the FLUSH attribute of a field to “true”, data changes are flushed to the server when the user leaves the field (“focusLost” event). By setting the FLUSHTIMER you can define that after a certain duration on inactivity changes are flushed to the server automatically.

In general: pay attention to correctly setting the FLUSH attribute! Be aware of cost of round trips in your and your customers' environment.

Flush Management (Container/Row Level)

In addition to the FLUSH definition on component level there is a flush management on container level. On any container/row level you can define the attribute “FLUSHAREA” to true or false. If setting the attribute to “true” then a roundtrip is triggered to the server side if ...

Background Painting (BACKGROUND and BGPAINT)

All of the container components and some of the column components provide the possibility to define the background coloring of the component in a quite sophisticated way.

The most plain way is to use the BACKGROUND color attribute. You can specify the background color in one of two ways:

Then there is the BGPAINT attribute: via BGPAINT you can execute a series of paint commands, each one being a command like “bgimage(center,/images/test.png)”, concatenated by semicolons.

The commands available area:

Color values in the BGPAINT command can be either defined using #RRGGBB or #RRGGBBTT, as described above.

When using a sequence of commands with BGPAINT then the paint commands are executed in exactly the sequence that you define.

Examples:

Singular BGPAINT definition:

 

bgimage(center,/images/test.png)

 

Concatenated BGPAINT definition:

 

background(#00000020,#00000010,vertical);bgimage(center,/images/test.png)

Focus Management – Setting Focus to dedicated Component

CaptainCasa Enterprise Client provides an explicit control over setting the keyboard focus to a dedicated component.

All input elements provide the attribute REQUESTFOCUS. You need to set this attribute for those components that you want to explicitly assign the keyboard focus to.

The most simple way of usage is to define a component's REQUESTFOCUS attribute to be the fix value “creation”:

...

<field ... requestfocus=”creation” ...>

 

In this case the keyboard focus of a page that is rendered within the client is automatically moved to this component. After being rendered the attribute will not have any further consequences – from now on the focus is following the user's navigation.

You also can control the focus from server side within a “live page”. For this reason you need to bind the REQUESTFOCUS attribute to a managed bean's property. The property must return a long-value. Every time you want a component to gain the focus, you need to update the value. For updating you use a utility Class “RequestFocusManager”.

Have a look onto the following scenario:

By using the combo box, a field is selected that then gains the focus.

In the layout definition each FIELD component is bound to a different property for controlling the requesting of the focus:

<t:rowdemobodypane id="g_3" objectbinding="demoRequestfocus" rowdistance="2" >

  <t:row id="g_4" >

    <t:label id="g_5" text="First Name" width="120" />

    <t:field id="g_6" requestfocus="#{d.demoRequestfocus.firstNameRF}" text="#{d.demoRequestfocus.firstName}" width="200" />

  </t:row>

  <t:row id="g_7" >

    <t:label id="g_8" text="Last Name" width="120" />

    <t:field id="g_9" requestfocus="#{d.demoRequestfocus.lastNameRF}" text="#{d.demoRequestfocus.lastName}" width="200" />

  </t:row>

  <t:row id="g_10" >

    <t:label id="g_11" text="Street" width="120" />

    <t:field id="g_12" requestfocus="#{d.demoRequestfocus.streetRF}" text="#{d.demoRequestfocus.street}" width="200" />

  </t:row>

  <t:row id="g_13" >

    <t:label id="g_14" text="ZipCode" width="120" />

    <t:field id="g_15" requestfocus="#{d.demoRequestfocus.zipCodeRF}" text="#{d.demoRequestfocus.zipCode}" width="200" />

  </t:row>

  <t:row id="g_16" >

    <t:label id="g_17" text="City" width="120" />

    <t:field id="g_18" requestfocus="#{d.demoRequestfocus.cityRF}" text="#{d.demoRequestfocus.city}" width="200" />

  </t:row>

  <t:rowdistance id="g_19" height="20" />

  <t:row id="g_20" >

    <t:coldistance id="g_21" width="120" />

    <t:combobox id="g_22" actionListener="#{d.demoRequestfocus.onFocussedFieldAction}" flush="true" value="#{d.demoRequestfocus.focussedField}" width="120" >

      <t:comboboxitem id="g_23" text="firstName" value="firstName" />

      <t:comboboxitem id="g_24" text="lastName" value="lastName" />

      <t:comboboxitem id="g_25" text="street" value="street" />

      <t:comboboxitem id="g_26" text="zipCode" value="zipCode" />

      <t:comboboxitem id="g_27" text="city" value="city" />

    </t:combobox>

  </t:row>

</t:rowdemobodypane>

 

When the user selects the combo box then a corresponding method is invoked on server side:

package workplace;

 

...

import org.eclnt.jsfserver.session.RequestFocusManager;

 

public class DemoRequestfocus extends DemoBase

{

    protected long m_firstNameRF;

    protected long m_lastNameRF = RequestFocusManager.getCreationRequestFocusCounter();

    protected long m_streetRF;

    protected long m_cityRF;

    protected long m_zipCodeRF;

    public long getFirstNameRF() { return m_firstNameRF; }

    public long getLastNameRF() { return m_lastNameRF; }

    public long getStreetRF() { return m_streetRF; }

    public long getCityRF() { return m_cityRF; }

    public long getZipCodeRF() { return m_zipCodeRF; }

 

    protected String m_firstName;

    protected String m_lastName;

    protected String m_street;

    protected String m_zipCode;

    protected String m_city;

    public void setFirstName(String value) { m_firstName = value; }

    public String getFirstName() { return m_firstName; }

    public void setLastName(String value) { m_lastName = value; }

    public String getLastName() { return m_lastName; }

    public String getStreet() { return m_street; }

    public void setStreet(String value) { m_street = value; }

    public String getZipCode() { return m_zipCode; }

    public void setZipCode(String value) { m_zipCode = value; }

    public String getCity() { return m_city; }

    public void setCity(String value) { m_city = value; }

 

    protected String m_focussedField;

    public String getFocussedField() { return m_focussedField; }

    public void setFocussedField(String value) { m_focussedField = value; }

    

    public void onFocussedFieldAction(ActionEvent event)

    {

        if (m_focussedField == null)

            return;

        else if (m_focussedField.equals("firstName"))

            m_firstNameRF = RequestFocusManager.getNewRequestFocusCounter();

        else if (m_focussedField.equals("lastName"))

            m_lastNameRF = RequestFocusManager.getNewRequestFocusCounter();

        else if (m_focussedField.equals("street"))

            m_streetRF = RequestFocusManager.getNewRequestFocusCounter();

        else if (m_focussedField.equals("zipCode"))

            m_zipCodeRF = RequestFocusManager.getNewRequestFocusCounter();

        else if (m_focussedField.equals("city"))

            m_cityRF = RequestFocusManager.getNewRequestFocusCounter();

    }

 

}

 

The value for the properties that control the focus management is taken from the class RequestFocusManager. This class provides two methods:

Please note: when working with grids there is an additional function. You may set the focus to a certain grid row, by using the function FIXGRIDBinding.selectAndFocus(item).

Attribute TRIGGER

Some components provide a property containing the name “trigger”. These components provide some client side function that you need to invoke from server side.

Example: the PAINTAREA component provides an attribute TRIGGER: if the trigger is invoked then a certain animation is done on client side. Or: the JRPRINTER (Jasper Reports printer) component can be invoked using its attribute TRIGGERPRINT in order to print out some reports on client side.

Triggers in general are treated in the following way:

There is a class “org.eclnt.jsfserver.elements.util.Trigger” which you should directly use as property value, that you bind via expression. Have a look onto the following example, printing out a Jasper report on request:

Layout:

<f:view>

<h:form>

<f:subview id="workplace_demojrprinterg_21">

<t:beanprocessing id="g_1" >

  <t:jrprinter id="g_2" jasperxml="#{d.DemoJrprinter.jasperXml}" triggerprint="#{d.DemoJrprinter.triggerPrint}" withprintdialog="#{d.DemoJrprinter.withDialog}" />

</t:beanprocessing>

<t:rowdemobodypane id="g_3" objectbinding="#{d.DemoJrprinter}">

  <t:row id="g_4" >

    <t:checkbox id="g_5" selected="#{d.DemoJrprinter.withDialog}" text="Show printer dialog before printing" />

  </t:row>

  <t:rowdistance id="g_6" height="10" />

  <t:row id="g_7" >

    <t:button id="g_8" actionListener="#{d.DemoJrprinter.onPrint}" text="Print" />

  </t:row>

</t:rowdemobodypane>

<t:pageaddons id="g_pa"/>

</f:subview>

</h:form>

</f:view>

 

Code:

package workplace;

 

import java.io.Serializable;

 

import javax.faces.event.ActionEvent;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.elements.util.Trigger;

import org.eclnt.jsfserver.managedbean.IDispatcher;

import org.eclnt.util.file.ClassloaderReader;

import org.eclnt.util.file.FileManager;

 

@CCGenClass (expressionBase="#{d.DemoJrprinter}")

 

public class DemoJrprinter

    extends DemoBase

    implements Serializable

{

    public DemoJrprinter(IDispatcher dispatcher)

    {

        super(dispatcher);

    }

 

    protected boolean m_withDialog = true;

    public boolean getWithDialog() { return m_withDialog; }

    public void setWithDialog(boolean value) { m_withDialog = value; }

 

    protected Trigger m_triggerPrint = new Trigger();

    public Trigger getTriggerPrint() { return m_triggerPrint; }

    public void setTriggerPrint(Trigger value) { m_triggerPrint = value; }

 

    protected String m_jasperXml;

    public String getJasperXml() { return m_jasperXml; }

    public void setJasperXml(String value) { m_jasperXml = value; }

 

    public void onPrint(ActionEvent event)

    {

        String s = (new ClassloaderReader()).readUTF8File("workplace/resources/jasperxmlexport.xml",true);

        m_jasperXml = s;

        m_triggerPrint.trigger();

    }

 

}

Other Layout Managers

The component structure “container – row – component” that was introduced at the beginning of this chapter is the general structure that is used for defining the layout of pages.

In addition there are some additional components which are arranging contained components in a slightly different way.

GRIDLAYOUTPANE – Grid Layout Manager

The GRIDLAYOUTPANE arranges its content in a matrix that is defined by its content.

Example: the following layout...

...is defined in the following way:

<t:gridlayoutpane id="g_30" border="#00000010" coldistance="5"

    padding="10" rowdistance="5">

    <t:gridlayoutrow id="g_31">

        <t:label id="g_32" text="Short text" />

        <t:field id="g_33" width="200" />

        <t:button id="g_34" text="Button 1" />

    </t:gridlayoutrow>

    <t:gridlayoutrow id="g_35">

        <t:label id="g_36" text="Loooooonnngggg ttteeexttt" />

        <t:field id="g_37" width="100" />

        <t:button id="g_38" text="OK" />

    </t:gridlayoutrow>

</t:gridlayoutpane>

 

Different to the default Container/Row-arrangement of controls, the GRIDLAYOUTPANE container arranges its content, so that columns are synchronized over all its contained rows.

Internally the positioning is done via matrix coordinates, but for simplicity reason this is hidden by arranging components in GRIDLAYOUTROW sub-components. For sizing you may use “just as normal” both percentage sizes or pixel sizes - or no size at all, which means that the component is rendered with its minimum size.

You may define a spanning for components – both in horizontal and vertical direction:

The layout definition now is:

<t:gridlayoutpane id="g_66" border="#00000010" coldistance="5"

    padding="10" rowdistance="5" width="100%">

    <t:gridlayoutrow id="g_67">

        <t:label id="g_68" colalignmentx="right" text="Short text" />

        <t:field id="g_69" width="100%;200" />

        <t:button id="g_70" text="Button 1" />

        <t:label id="g_71" align="center" background="#C0FFC0"

            height="100%" rowspan="3" text="Side label" width="100" />

    </t:gridlayoutrow>

    <t:gridlayoutrow id="g_72">

        <t:label id="g_73" colalignmentx="right"

            text="Loooooonnngggg ttteeexttt" />

        <t:field id="g_74" width="100" />

        <t:button id="g_75" text="OK" />

    </t:gridlayoutrow>

    <t:gridlayoutrow id="g_76">

        <t:label id="g_77" align="center" background="#C0FFC0"

            colspan="3" height="50" text="Bottom label" width="100%" />

    </t:gridlayoutrow>

</t:gridlayoutpane>

 

If a matrix cell exceeds the size of a contained component then you can define the position of the component by using the attributes:

Example: the text of the one label component is much shorter than the text of the label component. Due to the synchronized column sizing the short label by default positions on the left side of the available matrix cell. But: you can override...

... by setting the COLALIGNMNETX accordingly:

<t:gridlayoutpane id="g_66" border="#00000010" coldistance="5"

    padding="10" rowdistance="5" width="100%">

    <t:gridlayoutrow id="g_67">

        <t:label id="g_68" colalignmentx="right" text="Short text" />

            ...

    </t:gridlayoutrow>

    <t:gridlayoutrow id="g_72">

        <t:label id="g_73" colalignmentx="right"

            text="Loooooonnngggg ttteeexttt" />

            ...

COLSYNCHEDPANE, COLSYNCHEDPANEROW – Connected Columns

(The COLSYNCHEDPANE/ROW layout management is part of the more generic GRIDLAYOUTPANE. Please use the GIRDLAYOUTPANE. This chapter still is part of the documentation for users for the COLSYNCHEDPANE container.)

The COLSYNCHEDPANE component is a container, in which its contained rows (of type COLSYNCHEDPANEROW) are not rendered independent from another, but are rendered in a “connected” way.

Take a look onto the following screen shot:



The layout definition is:

<t:colsynchedpane id="g_5" background="#00000030"

    border="#00000030" coldistance="5" padding="10" rowdistance="5">

    <t:colsynchedrow id="g_6">

        <t:label id="g_7" text="First Name" width="100" />

        <t:field id="g_8" width="100" />

        <t:button id="g_9" text="Clear" />

    </t:colsynchedrow>

    <t:colsynchedrow id="g_10">

        <t:label id="g_11" text="Last Name" width="100" />

        <t:field id="g_12" width="100" />

        <t:button id="g_13" text="Clear" />

    </t:colsynchedrow>

    <t:colsynchedrowdistance id="g_14" height="20" />

    <t:colsynchedrow id="g_15">

        <t:label id="g_16" text="Marital Status" width="100" />

        <t:checkbox id="g_17" text="Married" />

        <t:button id="g_18" text="Clear" />

    </t:colsynchedrow>

</t:colsynchedpane>

 

Each row consists out of three components. You see that the components are arranged in a common column structured: the button of the third row is not directly positioned behind the check box (as it would have happened in a normal ROW component) – but is aligned to the column structure of the columns above.

You may use a COLSPAN attribute that is available in each component to identify that one component overlaps several other components of other rows.

This COLSYNCHEDPANE reminds a bit to the HTML-Table (<table>...</table>) definition – with all its positive and negative consequences: when adding a control into one row of the table then you have to update all other rows and e.g. adjust the COLSPAN definitions.

OVERLAYAREA, OVERLAYAREAITEM

In some cases you want to position components one on top of the other. This is the purpose of the OVERLAYAREA – which opens up some area, in which OVERLAYREAITEM instances are arranged. Each OVERLAYAREAITEM has some coordinates (x,y,width,height) and some z-position, on order to define which component is on top of which other component.

Example: in a grid you want to show the user that not all data which is potentially available really is loaded (e.g. for performance reason):

You want to overlay this information on top of the grid so that no additional vertical screen space is required – and so that the message is clearly visible to the user.

The layout definition is:

<t:row id="g_5">

    <t:overlayarea id="g_6" background="#f0f0f0" height="100%"

        width="100%">

        <t:overlayareaitem id="g_7"

            height="100%"

            width="100%"

            x="0"

            y="0"

            zindex="1">

            <t:fixgrid id="g_8" height="100%"

                objectbinding="#{d.DemoPaintAreaUsage.grid}"

                sbvisibleamount="35" width="100%">

                          ...

                       ...

            </t:fixgrid>

        </t:overlayareaitem>

        <t:overlayareaitem id="g_13" background="#FFC0C0C0"

            border="left:1;right:1;top:1;bottom:0;color:#00000030"

            boundsanchor="centerbottom"

            height="#{d.DemoPaintAreaUsage.messaageAraeHeight}"

            width="#{d.DemoPaintAreaUsage.messageAreaWidth}"

            x="50%"

            y="100%"

            zindex="2">

            <t:pane id="g_14" padding="left:10;right:10;top:0;bottom:0">

                <t:row id="g_15">

                             ...

                    <t:label id="g_17" align="center"

                        font="weight:bold"

                        height="100%"

                        text="Too many items found! Please

                                            restrict your selection."

                        width="100%" />

                </t:row>

            </t:pane>

        </t:overlayareaitem>

    </t:overlayarea>

</t:row>

 

The width/height of the message area is bound to properties of a program. At runtime the values are either “0/0” if no message should occur or e.g. “400/100” if the message should occur.

The positioning of the OVERLAYAREAITEM is quite flexible:

OVERLAYAREA and OVERLAYAREAITEM are used both for “big layouting issues” (such as the example: a message area in front of a grid area) – and for “small layouting issues” (such as the number of messages on top of an icon component).

Grid Components

The FXIGRID component is the component for building grids. Grids include:

The FIXGRID arranges any type of component in a grid of fixed columns, that's where the name comes from. The type of components is up to you, i.e. you can arrange labels, fields, combo boxes, buttons, etc.

The FIXGRID can hold any number of rows - on the server side. On client side only those rows are loaded and presented that the user currently sees. When scrolling through the grid the grid automatically updates its items from the server. We call this “server side scrolling”.

FIXGRID Basics

FIXGRID, GRIDCOL, GRIDHEADER, GRIDFOOTER

A FIXGRID component is structured in the following way:

FIXGRID

    * GRIDCOL

        <column component>

    * GRIDHEADER

        <header cell component>

    * GRIDFOOTER

        <footer cell component>

 

Each grid has a series of columns, per column you define one GRIDCOL component defines the initial width and the text of the column.

Inside the GRIDCOL component you define exactly one content component, i.e. you define if the column's cells should be a FIELD or a LABLE or whatever control. It is possible to define a PANE as content component – and inside the PANE to open up any content. So any content can be placed inside the GRIDCOL components, but there must only exist one component within the GRIDCOL definition itself which represents the cell content.

Examples:

 

GRIDCOL           <== valid

  LABEL

GRIDCOL           <== valid

  FIELD

GRIDCOL           <== valid

  PANE

    ROW

      LABEL

      LABEL

 

The grid may have header rows and footer rows. Per row you define a corresponding GRIDHEADER or GRIDFOOTER component.

Have a look at the example “demominispread.jsp”:

The JSP layout definition is:

<t:fixgrid id="g_5" background="#C0C0C0" borderheight="1" borderwidth="1"

           height="100%" objectbinding="#{d.demoMinispeadsheet.sheet}"

           sbvisibleamount="20" width="100%" >

 

    <t:gridcol id="g_6" background="#E0E0E0" text="Region" width="100" >

        <t:field id="g_7" background="#FFFFFF" text=".{region}" />

    </t:gridcol>

 

    <t:gridcol id="g_8" background="#E0FFE0" text="Revenue" width="34%" >

        <t:formattedfield id="g_9" background="#FFFFFF" format="double"

                          value=".{revenue}" />

    </t:gridcol>

 

    <t:gridcol id="g_10" background="#FFE0E0" text="Cost" width="33%" >

        <t:formattedfield id="g_11" format="double" value=".{cost}" />

    </t:gridcol>

 

    <t:gridcol id="g_12" background="#E0E0E0" text="Profit" width="33%" >

        <t:formattedfield id="g_13" background=".{profitColor}" enabled="false"

                          format="double" value=".{profit}" />

    </t:gridcol>

 

    <t:gridfooter id="g_14" >

        <t:coldistance id="g_15" />

        <t:formattedfield id="g_16" background="#E0FFE0" enabled="false"

             format="double" value="#{d.demoMinispeadsheet.revenueAll}" />

        <t:formattedfield id="g_17" background="#FFE0E0" enabled="false"

             format="double" value="#{d.demoMinispeadsheet.costAll}" />

        <t:formattedfield id="g_18"

             background="#{d.demoMinispeadsheet.profitAllColor}"

             enabled="false" format="double"

             value="#{d.demoMinispeadsheet.profitAll}" />

    </t:gridfooter>

 

</t:fixgrid>

 

The FIXGRID component defines the grid itself: it defines the sizing (WIDTH/HEIGHT) it defines the number of lines in the client (SBVISIBLEAMOUNT) and it defines the binding to the server side representation of the grid (OBJECTBINDING).

The grid contains 4 GRIDCOL column definitions, the content of the columns either is a FIELD or a FORMATTEDFIELD component.

The grid contains one GRIDFOOTER definition, containing the components which are shown as footer row.

Data Binding

A grid is reflected by a collection of objects on the server side. Each object represents a row of the grid.

The “entrance” into the grid processing is done by an instance of class FIXGRIDListBinding (for lists) and FIXGRIDTreeBinding (for trees).

This FIXGRID-object on server side is referenced by the OBJECTBINDING attribute of the FIXGRID.

Let's focus on list processing first, trees will follow later on:

The FIXGRIDListBinding supports a method getItems() which passes back a java.util.List interface in which you now can add your data rows.

Note: adding data rows on server side does not mean that all the data is transferred to the client. If adding 1000 rows on server side, and only 10 rows are displayed on client side, then as consequence also only 10 rows will be loaded into the client. The FIXGRIDListBinding unburdens you from the task of scrolling through the rows.

Each data object that you now put into a FIXGRIDListBinding collection needs to provide the properties that are referenced by the controls inside the GRIDCOL definitions.

You may already have noted that inside the property definitions of FIELD and FORMATTEDFIELD components that are located below GRIDCOL components, the expression “.{<property>}” is used. This expression indicates that the property value is taken from the object that represents the row.

Have a look at the implementation which belongs to the “demominispread.jsp” demo mentioned above:

public class MyRow extends FIXGRIDItem

{

    String m_region;

    double m_revenue;

    double m_cost;

    public void setRegion(String value) { m_region = value; }

    public String getRegion() { return m_region; }

    public double getRevenue() { return m_revenue; }

    public void setRevenue(double value) { m_revenue = value; }

    public double getCost() { return m_cost; }

    public void setCost(double value) { m_cost = value; }

    public double getProfit() { return m_revenue - m_cost; }

    public String getProfitColor() { if (getProfit()<0) return "#FFE0E0"; else return "#E0FFE0"; }

}

 

FIXGRIDListBinding<MyRow> m_sheet = new FIXGRIDListBinding<MyRow>();

 

public DemoMinispread()

{

    MyRow r;

    r = new MyRow(); r.m_region = "North"; m_sheet.getItems().add(r);

    r = new MyRow(); r.m_region = "East"; m_sheet.getItems().add(r);

    r = new MyRow(); r.m_region = "South"; m_sheet.getItems().add(r);

    r = new MyRow(); r.m_region = "West"; m_sheet.getItems().add(r);

}

 

public FIXGRIDListBinding<MyRow> getSheet() { return m_sheet; }

 

public void onCreateRow(ActionEvent event)

{

    MyRow r = new MyRow();

    r.m_region = "new";

    m_sheet.getItems().add(r);

}

 

There is the “sheet” property of type FIXGRIDListBinding. It is filled with objects of type “MyRow”, that internally provides the properties “region”, “revenue”, etc. These are exactly the properties that are referenced by the FIELD and FORMATTEDFIELD definitions of the layout.

You can also see how objects are added into the grid: MyRow objects are created and simply added to the “sheet” property.

Consequence: on server side, the grid is managed as just normal collection.

Event Binding

There are two special events that you get notified on server side:

The events are represented by the protected methods:

Both methods are part of the FIXGRIDItem class which is used as base class for the row object.

Rendering Aspects

The FIXGRID component provides a number of attributes to control the rendering. There are some aspects to be aware of:

Columns Sizing Aspects

The WIDTH attribute of the GRIDCOL by default is responsible for sizing the corresponding column. You can define the following types of values:

By default the sizing of the grid columns only depends on the WIDTH definition, as shown. If the components within the grid column cells do not fit in, then they are cut accordingly.

But, there is also the possibility to derive the width of a column from its content cells. This is the purpose of the attribute GRIDCOL-DYNAMICWIDTHSIZING. While rendering the grid on client side, the client will measure the size of each cell within a certain column – and the whole column will be sized according to the widest cell.

When using GRIDCOL-DYNAMICWIDTHSIZING you can in addition still maintain the WIDTH attribute. The interpretation is as follows:

Please pay attention when using dynamic width sizing;

...some more info about “Server side number of Items” versus “Client Side number of Items”

You can skip this chapter when first time using grids ...or you can immediately read and get some deeper understanding of how the grid internally operates:

The FIXGRID component is able to handle grids with a large amount of items. The simple reason for this: only these items are sent to the client and as consequence are potentially rendered in the client, that are currently relevant in the client.

This means: the grid processing on server side may handle a grid with 10.000 items – but actually only a few of them are sent to the client. This ensures a scalability both from network volume and from rendering performance perspective. - Of course there is some “side-effect”: when the client only knows few of the items, then it has to talk to the server during scroll operations in order to update.

There is one important attribute, ruling all this – which is the attribute FIXGRID-SBVISIBLEAMOUNT. The server will always send the number of items to the client that is defined with SBVISIBLEAMOUNT. So, if SBVISIBLEAMOUNT is defined as “10” and the grid is newly rendered then by default the items with the index 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 are transferred to the client side – only if they are available at all.

The property FIXGRDIBinding-sbvalue is relevant for telling the grid, where the current “area of interest” is. So, at the beginning it is by default “0”, when the user scrolls to a certain position it is updated accordingly. You may also set sbvalue from your server side program in case you want to scroll to a certain item of the grid.

Now let's take a look onto the rendering. There are two cases:

You see: when explicitly defining a HEIGHT with a grid, you need to think a bit about how to set SBVISBLEAMOUNT:

By the way: the SBVISBLEAMOUNT of the grid can not be changed once a grid is rendered! You must set it correctly from the beginning on.

Tree Processing

A tree is a normal FIXGRID implementation with three special things:

All the other things stay the same: you build the tree on server side, the transfer of the visible rows to the client is automatically done.

Have a look onto the following example:

The tree is a FIXGRID component containing two GRIDCOL columns: one with a TREENODE component inside, the other one with a FORMATTEDFIELD component:

<t:rowdemobodypane id="g_3" objectbinding="demoTree" >

    <t:row id="g_4" >

        <t:fixgrid id="g_5" avoidroundtrips="true" bordercolor="#D0D0D0"

                   borderheight="0" borderwidth="1" height="100%"           

                   objectbinding="#{d.demoTree.tree}" rowheight="16"

                   sbvisibleamount="25" width="100%" >

            <t:gridcol id="g_6" text="Country / Town" width="100%" >

                <t:treenode id="g_7" />

            </t:gridcol>

            <t:gridcol id="g_8" text="Inhabitants" width="100" >

                <t:formattedfield id="g_9" align="right" format="int"

                                  value=".{inhabitants}" />

            </t:gridcol>

        </t:fixgrid>

    </t:row>

    <t:row id="g_10" >

        <t:button id="g_11" actionListener="#{d.demoTree.onOpenAllNodes}"

                  text="Open all" />

        <t:button id="g_12" actionListener="#{d.demoTree.onCloseAllNodes}"

                  text="Close all" />

    </t:row>

</t:rowdemobodypane>

 

The server side program shows that the grid processing is very similar to the normal grid processing:

package workplace;

 

import java.util.List;

 

import javax.faces.event.ActionEvent;

 

import org.eclnt.jsfserver.defaultscreens.Statusbar;

import org.eclnt.jsfserver.elements.impl.FIXGRIDTreeBinding;

import org.eclnt.jsfserver.elements.impl.FIXGRIDTreeItem;

 

public class DemoTree extends DemoBase

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

 

    public class MyRow extends FIXGRIDTreeItem

    {

        int i_inhabitants;

        public MyRow(FIXGRIDTreeItem parent, String text, int inhabitants, boolean isEndNode)

        {

            super(parent);

            setText(text);

            i_inhabitants = inhabitants;

            if (isEndNode)

                setStatus(STATUS_ENDNODE);

        }

        public void setInhabitants(int value) { i_inhabitants = value; }

        public int getInhabitants() { return i_inhabitants; }

        public void onToggle()

        {

            Statusbar.outputMessage("TOGGLE on " + getText());

        }

        public void onRowExecute()

        {

            if (getStatusInt() == STATUS_CLOSED) setStatus(STATUS_OPENED);

            else if (getStatusInt() == STATUS_OPENED) setStatus(STATUS_CLOSED);

        }

        public void onRowSelect()

        {

            Statusbar.outputMessage("SELECT on " + getText());

        }

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    FIXGRIDTreeBinding<MyRow> m_tree = new FIXGRIDTreeBinding<MyRow>();

    

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    public DemoTree()

    {

        // fill the tree

        MyRow country;

        MyRow town;

        country = new MyRow(m_tree.getRootNode(),"Germany",80000000,false);

        town    = new MyRow(country,"Bammental",6800,true);

        town    = new MyRow(country,"Berlin",3000000,true);

        town    = new MyRow(country,"Munich",1000000,true);

        town    = new MyRow(country,"Hamburg",1200000,true);

        country = new MyRow(m_tree.getRootNode(),"Liechtenstein",20000,false);

        town    = new MyRow(country,"Vaduz",20000,true);

        country = new MyRow(m_tree.getRootNode(),"United Kingdom",60000000,false);

        town    = new MyRow(country,"London",6000000,true);

        town    = new MyRow(country,"Manchester",500000,true);

        country = new MyRow(m_tree.getRootNode(),"United States",280000000,false);

        town    = new MyRow(country,"Los Angeles",5000000,true);

        town    = new MyRow(country,"New York",9000000,true);

        town    = new MyRow(country,"San Francisco",1000000,true);

    }

    

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    public FIXGRIDTreeBinding<MyRow> getTree() { return m_tree; }

    

    public void onOpenAllNodes(ActionEvent ae)

    {

        setStatusInFolderNodes(m_tree.getRootNode(),FIXGRIDTreeItem.STATUS_OPENED);

    }

    

    public void onCloseAllNodes(ActionEvent ae)

    {

        setStatusInFolderNodes(m_tree.getRootNode(),FIXGRIDTreeItem.STATUS_CLOSED);

    }

    

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

 

    private void setStatusInFolderNodes(FIXGRIDTreeItem node, int status)

    {

        List<FIXGRIDTreeItem> nodes = node.getChildNodes();

        for (int i=0; i<nodes.size(); i++)

        {

            FIXGRIDTreeItem childNode = nodes.get(i);

            if (childNode.getStatusInt() != childNode.STATUS_ENDNODE)

            {

                childNode.setStatus(status);

                setStatusInFolderNodes(childNode,status);

            }

        }

    }

    

}

 

Some aspects to put focus on:

Please have a look into the JavaDoc documentation and into the examples of the demo workplace in order to find more information about implementing trees.

Column Sequence and Column Sizing

This is something for a bit more advanced programmers, but not too complex as well: influencing the sequence of columns in a grid, and influencing the widths of the columns.

Column Sequence

By default a user can rearrange the columns of a grid on his/her own by dragging and dropping the headers of a column onto one another.

If the user does so, a property will automatically be populated inside the server side FIXGRIDBinding object. The property “columnsequence” then contains a semicolon-separated value which indicates the new sequence of columns.

FIXGRIDBinding

  getColumnsequence

  setColumnsequence

 

Example: if a grid has 4 columns, and the user arranges the last column to be the first one, then the value of the property will be:

3;0;1;2

 

This means: the first visible column is the original column 3; the second column is the original column 0, etc.

The same property can also be used for setting the column sequence. Thus, you can override the column sequence that is given by the layout definition.

Consequence: if you want to provide personalizable grids to your user, in which the sequence of columns is kept with the user profile, you can do so!

Column Widths

The original columns widths are taken out of the GRIDCOL definitions that are part of the layout definition.

Something similar, compared to the column sequence management, happens within the column width management: when the user starts to change the columns widths, a property will be populated on server side. The property's name is “modcolumnwidths”:

FIXGRIDBinding

  getModcolumnwidths

  setModcolumnwidths

 

Now the property contains the widths of the columns, as modified by the user. The property might look like:

132;50%;100;50%

 

Note: the sequence of widths is representing the original sequence of columns as defined within the layout definition. The sequence is not dependent on the visible sequence of columns.

When setting the property from your server side program the widths will be applied to the current grid.

Persistence Management with FIXGRIDs

There is a pre-thought persistence management available – in which the column sequence and column sizes are persisted. The persistence management is invoked when defining a PERSISTID with your FIXGRID. The PERSISTID is the unique used for identifying the corresponding grid and the column data that is stored for this grid.

Please check the interface “IFIXGRIDPersisetence2” and the default implementation “DefaultFIXGRIDPersistence” for more information.

Column Sorting

The FIXGRID provides a nice way of sorting its items. There is an attribute SORTREFERENCE that is defined with the GRIDCOL component. When defined, the grid will automatically sort its content once the user clicks with the mouse onto the title of the column.

The straight forward Way

The SORTREFERENCE is an expression that points to the property that is used for sorting. The expression is built in exactly the same way as expressions which are used within the grid's cell components: “.{xyz}”.

Have a look at the following example:

The first column indicates that it is sorted in an ascending way. Once the user clicks the column header the sorting will change accordingly.

The JSP layout definition is:

<t:fixgrid id="g_5" avoidroundtrips="true" background="#F0F0F0"

           bordercolor="#C0C0C0" borderheight="1" borderwidth="1"

           height="100%" objectbinding="#{d.demoGrid.rows}"

           sbvisibleamount="25" width="100%" >

 

    <t:gridcol id="g_6" sortreference=".{firstName}" text="First Name"

               width="50%" >

        <t:field id="g_7" text=".{firstName}" />

    </t:gridcol>

 

    <t:gridcol id="g_8" sortreference=".{lastName}" text="Last Name"

               width="50%" >

        <t:field id="g_9" text=".{lastName}" />

    </t:gridcol>

 

    <t:gridcol id="g_10" sortreference=".{married}" text="Married" width="50" >

        <t:checkbox id="g_11" align="center" background="#FFFFFF"

                    selected=".{married}" />

    </t:gridcol>

 

    <t:gridcol id="g_12" width="80" >

        <t:button id="g_13" actionListener=".{onRemove}"

                            contentareafilled="false" text="Remove" />

    </t:gridcol>

 

</t:fixgrid>

 

You see:

When looking at the code you will see that all sort operations by default are provided without affecting your code:

package workplace;

 

import javax.faces.event.ActionEvent;

 

import org.eclnt.jsfserver.elements.impl.FIXGRIDItem;

import org.eclnt.jsfserver.elements.impl.FIXGRIDListBinding;

 

public class DemoGrid extends DemoBase

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

 

    public class MyRow extends FIXGRIDItem

    {

        String i_firstName;

        String i_lastName;

        boolean i_married;

        // --------------------------------------------------------------------

        public void setFirstName(String value) { i_firstName = value; }

        public String getFirstName() { return i_firstName; }

        public void setLastName(String value) { i_lastName = value; }

        public String getLastName() { return i_lastName; }

        public void setMarried(boolean value) { i_married = value; }

        public boolean getMarried() { return i_married; }

        // --------------------------------------------------------------------

        public void onRemove(ActionEvent ae)

        {

            m_rows.getItems().remove(this);

        }

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    FIXGRIDListBinding<MyRow> m_rows = new FIXGRIDListBinding<MyRow>();

    

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    public DemoGrid()

    {

        // create some dummy items

        MyRow row;

        row = new MyRow();

        row.setFirstName("Michael");

        row.setLastName("Schumacher");

        row.setMarried(true);

        m_rows.getItems().add(row);

        row = new MyRow();

        row.setFirstName("Alain");

        row.setLastName("Prost");

        row.setMarried(true);

        m_rows.getItems().add(row);

        row = new MyRow();

        row.setFirstName("Niki");

        row.setLastName("Lauda");

        row.setMarried(true);

        m_rows.getItems().add(row);

        row = new MyRow();

        row.setFirstName("Nik");

        row.setLastName("Heidfeld");

        row.setMarried(false);

        m_rows.getItems().add(row);

    }

    

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    public FIXGRIDListBinding<MyRow> getRows() { return m_rows; }

    

    public void onAddRow(ActionEvent ae)    

    {

        MyRow row = new MyRow();

        row.setFirstName("new");

        row.setLastName("new");

        m_rows.getItems().add(row);

    }

    

}

 

The default sorting checks the data type of a property during sorting. The following data types are supported:

All other types are converted into strings (by using .toString() method), the sorting then will be done using those strings.

If SORTREFERENCE is undefined...

If you do not explicitly define the SORTREFERENCE as shown in the previous text then the system tries to find it on its own. It takes a look into the component that is directly located within the GRIDCOL element – which is the cell component. Then it checks for values available within the TEXT, VALUE or other attributes and uses the corresponding expression as SORTREFERENCE.

This is the reason why you typically do not have to define a SORTREFERENCE when just defining a LABEL or a FIELD as cell component.

But, please note: this automatic “guessing” is only executed on the top most component within a GRIDCOL.

Directly setting the Sort Status

For each grid columns that explicily or implicitly holds a SORTREFERENCE you can access the sort reference object in the following way:

public interface IFIXGRIDBinding<itemClass extends FIXGRIDItem>

{

    /**

     * Sort information that is kept per sortable column.

     */

    public static class FIXGRIDSortInfo

    {

        int i_sortStatus = SORT_UNSORTED;

        public String getSortStatus()

        {

            switch (i_sortStatus)

            {

                case SORT_UNSORTED:

                    return null;

                case SORT_ASCENDING:

                    return "ascending";

                case SORT_DESCENDING:

                    return "descending";

            }

            throw new Error("Should never happen: " + i_sortStatus);

        }

        public void setSortStatus(int sortStatus)

        {

            i_sortStatus = sortStatus;

        }
       ...

    }

    

    static final int SORT_UNSORTED = 0;

    static final int SORT_ASCENDING = 1;

    static final int SORT_DESCENDING = 2;

 

    ...

    ...

    ...

    

    public FIXGRIDSortInfo getSortInfoForReference(String sortReference);

}

 

The server side grid collections (FIXGRIDListBinding or FIXGRIDTreeBinding) support the interface IFIXGRIDBinding. IFIXGRIDBinding includes the definition of a FIXGRIDSortInfo class, and it allows to access the sort info by using the method “getSortInfoForReference”.

By using the method “setSortStatus()” you may initialize the sort status of a column on your own. Maybe you already read the data from the database in a sorted way and you want to pass this information on to the user.

Please pay attention: “getSortInfoFotReference(...)” is the only correct way to access the FIXGRIDSortInfo object – do not directly acces the map of FIXGRIDSortInfo objects that is available as well!

And: the value that you pass as “sortReference” parameter is the explicit or implicit value of the attribute SORTREFERENCE, without expression-prefix and postfix. This means: if the SORTREFERENCE is defined as “.{lastName}” then the value you pass as parameter is “lastName”.

Influencing the default Sorting

The default sorting scans the values of certain grid column and then compares them using the Java sorting that is part of “java.util.Collections”. For comparing the values it checks the data type of the values and chooses a corresponding comparator. Example: if the values are of type “Integer”, then a numeric comparator is chosen – if the values are of type “String” then a lexical Comparator is used.

You can explicitly define the comparator to be used during sorting by overriding the grid's method “FIXGRIDBinding.findSortComparatorForColumnValue(...)”:

protected Comparator findSortComparatorForColumnValue(String sortReference)

{

    ...

    // select your own Comparator for the corresponding sortReference

    // or passe super.findSortComparatorForColumnValue(sortReference) for

    // using the default comparator

    ...

}

 

Example for using this method: the data type of column values for a certain reason (e.g. accessing the database in a simple way) is “String”, but the contained data actually is of type integer. The default sorting would use a lexical comparator, while you tell to use a numeric one.

Implementing an own Sort Algorithm

In some cases you may want to implement your own algorithm for sorting. Maybe you do not want to sort the grid within memory but want to re-read the grid from the database, passing new sort commands with the SQL.

In this case you need to be aware of what internally happens when the user clicked with the mouse onto a sortable column:

    protected abstract void sortGrid(String sortReference,

                                     String objectBindingString,

                                     boolean ascending);

 

You can override the sortGrid() method within an own sub-class of FIXGRIDListBinding (or FIXGRIDTreeBinding) and implement your own sort algorithm. Within this sort algorithm you can perform any grid operation you like: you can remove grid items, add new ones, etc.

Multiple Column Sorting

In addition to the sorting by one column there is the so called multiple column sorting. The user invokes this sorting by holding down the ctrl-key when clicking onto the coulmn headers of the grid. The sequence in which the user clicks the header defines the priority of sorting.

The number of sort icons that is shown in the column header gives a visual feed back of the sort sequence:



In the example the first sort priority is the birthday, the second priority is the last name – followed by the first name.

Techincal Info behind

The multiple column sorting is managed on top of the normal column sorting.

The class FIXGRIDSortInfo that was introduced in the previous chapter holds a property “sortSequence” - with “0” being the first priority, “1” the seconds priority, etc.

The actual sorting is done by the method FIXGRIDBinding.processMultipleSort(). So this is the one to override when implementing own algorithms for multiple sorting.

The default implementation of the processMultipleSort() method internally calls the single column sorting (i.e. method FIXGRIDBinding.sortGrid(...)) multiple times – starting with the lowest sort priority first and then going back to the highest priority. The expectation behind is that the single column sorting algorithm is a stable sort. As consequence the result of processing one sort after the next is the correct multi-column sorting that you expect.

(Of course the default column sorting provided by CaptainCasa is a stable one, so there is no need to adapt anything!)

Usage from Server Side Java

You can directly sort the content of the grid by calling the following methods of FXIGRID(List/Tree)Binding:

            getSortInfo().clear();

 

            FIXGRIDSortInfo fsi;

            fsi = getSortInfoForReference("lastName");

            fsi.setSortSequence(0);

            fsi.setSortStatus(SORT_ASCENDING);

            fsi = getSortInfoForReference("firstName");

            fsi.setSortSequence(1);

            fsi.setSortStatus(SORT_ASCENDING);

 

            resort();

 

The steps are:

When initially sorting a grid then you need to pay attention to, that the grid must be bound to its component (FIXGRID) – otherwise the sorting will not work. The binding to the component with a fresh grid is done when the grid is processed in the JSF render phase the first time (this is when the page's XML is processed).

As consequence you need to shift the sort code into the initialize() method which is called when the component binds to the FIXGRIDBinding object. You need to override your FIXGRIDList/TreeBinding, e.g. in the following way:

    public class MyFIXGRIDListBinding extends FIXGRIDListBinding<MyRow>

    {

        public void initialize()

        {

            super.initialize();

            // this is how to sort by one column

//            {

//                sort("firstName",true);

//            }

            // this shows how to sroty by multiple columns

            {

                getSortInfo().clear();

                FIXGRIDSortInfo fsi;

                fsi = getSortInfoForReference("lastName");

                fsi.setSortSequence(0);

                fsi.setSortStatus(SORT_ASCENDING);

                fsi = getSortInfoForReference("firstName");

                fsi.setSortSequence(1);

                fsi.setSortStatus(SORT_ASCENDING);

                resort();

            }

        }

    }

Switching Off Multiple Column Sorting

In cases in which you want to switch off multi column sorting, just set the attribute FIXGRID-MULTICOLUMNSORT to “false” - then only single column sorting will be active.

Focus Issues

Setting the Focus into Grid Line

The typical way of setting the client keyboard focus to a certain component is by using the component attribute REQUESTFOCUS. The mechanism described in chapter “Rules that apply to all Components – Focus Management” is of course applicable for all components that you insert into grid rows as well.

There is an additional simplification function available in order to move the client focus into the first cell of a grid row. You simply need to call the function “FIXGRDBinding.selectAndFocus(FIXGRIDItem)”.

Please take a look at the following example:

public class DemoGrid extends DemoBase

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

 

    public class MyRow extends FIXGRIDItem

    {

        String i_firstName;

        String i_lastName;

        boolean i_married;

        // --------------------------------------------------------------------

        public void setFirstName(String value) { i_firstName = value; }

        public String getFirstName() { return i_firstName; }

        public void setLastName(String value) { i_lastName = value; }

        public String getLastName() { return i_lastName; }

        public void setMarried(boolean value) { i_married = value; }

        public boolean getMarried() { return i_married; }

        // --------------------------------------------------------------------

        public void onRemove(ActionEvent ae)

        {

            m_rows.getItems().remove(this);

        }

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    FIXGRIDListBinding<MyRow> m_rows = new FIXGRIDListBinding<MyRow>(true);

    

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    public DemoGrid()

    {

    }

    

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    public FIXGRIDListBinding<MyRow> getRows() { return m_rows; }

    

    public void onAddRow(ActionEvent ae)    

    {

        // add the item

        MyRow row = new MyRow();

        row.setFirstName("new");

        row.setLastName("new");

        m_rows.getItems().add(row);

        // select the item

        m_rows.deselectCurrentSelection();

        m_rows.selectAndFocusItem(row);

    }

    

}

 

You see that in the method “onAddRow” a new item is added to the grid. After adding the item first the current selection of the grid is removed. Then the method “selectAndFocusItem” is called. This method performs 3 steps:

Avoiding Line-Selection on Focus

By default lines of a grid are selected by the user in the following ways:

In certain constellations option C. is not adequate for line selection. E.g. when opening a popup dialog from a grid line and when changing the selection of the grid in the dialog, then the default behavior of the popup dialog when being close is to focus the component it was started from. If this is the grid – with some changed line selection – then the focus change will automatically select the line which was focused before opening the popup dialog.

You can switch off the option C. as consequence. Use attribute FIXGRID-SELECTONROWFOCUS and set value “false”.

Extended Grid Functions

Column Sequence

You may open a default dialog in which the user can explicitly define the sequence of columns and the visibility of columns.

Using the Columns Sequence Popup

The easiest way to include the columns sequence popup into your screen is by simply adding an invoker component (e.g. button, link) and referencing via expression to the “onOpenGridDetails”-method, that is directly available with all FIXGRIDBinding implementations on server side.

Example:

<t:row id="g_2">

    ...

    ...

    <t:link id="g_8"

        actionListener="#{d.DemoMinispread.sheet.onEditColumnDetails}"

        text="Columns..." />

    ...

    ...

</t:row>

<t:row id="g_9">

    <t:fixgrid id="g_10" avoidroundtrips="true" background="#FFFFFF80"

        bordercolor="#D0D0D0" borderheight="1" borderwidth="1"

        cellselection="true" drawoddevenrows="true" emptycolor="#F0F0F0"

        height="100%" multiselect="true"

        objectbinding="#{d.demoMinispread.sheet}" sbvisibleamount="20"

        width="100%">

    ...

    ...

 

You see: the grid (in t:fixgrid) is bound by using expression “#{d.demoMinispread.sheet}” - referencing on server side to FIXGRIDListBinding object. The method “onEditColumnsDetails” is directly available within this object.

As result the following popup dialog will be opened:


Search & Export

The server side grid management provides a couple of useful functions:

Using the Default Popup

The easiest way to include these functions is to embed the method “FIXGRIDBinding.onOpenGridFunctions(ActionEvent event)” into your screen, e.g. by providing a correpsonding LINK or BUTTON component.

Example: in the mini-budget demo of the demo workplace, the function is added as LINK component:

<t:row id="g_2">

    <t:coldistance id="g_3" width="100%" />

    <t:link id="g_4"

    actionListener="#{d.DemoMinispread.sheet.onOpenGridFunctions}"

        text="Grid..." />

</t:row>

<t:row id="g_5">

    <t:fixgrid id="g_6" avoidroundtrips="true" background="#FFFFFF80"

        bordercolor="#D0D0D0" borderheight="1" borderwidth="1"

        cellselection="true" emptycolor="#F0F0F0" height="100%"

        multiselect="true" objectbinding="#{d.demoMinispread.sheet}"

        sbvisibleamount="20" width="100%">

        ...

        ...

        ...

 

Result: a popup dialog will be opened once the user presses the LINK component:

The extended functions are provided within the popup.

Using the APIs

There are a couple of server side Java APIs that are provided by the FIXGRIDBinding class, which is the base for all server side grid implementations. Please check the JavaDoc documentation in the following areas:

All the functions that are provided in the default popup are available by an internal API as well.

“Internal” Details...

In case you do not like the default dialogs that are opened for defining the grid column sequence and/or the search & export dialog: here are some “internals”:

The code in FIXGRIDBinding for open the popup dialogs is:

public void onEditColumnDetails(ActionEvent ae)

{

    final DefaultScreens ds = DefaultScreens.getSessionAccess().getOwner();

    final GridDetails gd = DefaultScreens.getSessionAccess().getGridDetails();

    IGridDetailsListener gl = new IGridDetailsListener()

    {

        public void reactOnApplied()

        {

            ds.closePopup(gd);

        }

        public void reactOnClose()

        {

            ds.closePopup(gd);

        }

    };

    gd.prepare(this,gl,true,GridDetails.PAGENAME_COLUMNSDETAILS);

    ModelessPopup popup = ds.openModelessPopup(gd,"",400,400,new IModelessPopupListener()

    {

        public void reactOnPopupClosedByUser()

        {

            ds.closePopup(gd);

        }

    });

    popup.setUndecorated(true);

    popup.setCloseonclickoutside(true);

    popup.setStartfromrootwindow(false);

}

 

A page bean “DefaultScreens” is picked and opened as modeless dialog. The page bean is initialized using its “prepare(...)” method. The last parameter that is passed into the prepare method is the name of the .jsp/.xml-page that is to be used.

There are two static variables that are used:

Using own Page Definitions

In case you want to use other, own pages there are two ways:

Using own Page Beans

In case you may use own dialogs and not only applying optical changes to the default dialogs, but also adding new functions – you also need to somehow influence the way that the page bean for the dialogs is picked.

You do so by calling the static method “DefaultScreens.Factory.initClassGridDetails(..)”. Include the calling of this method into the start up process of your application.

Row Detail Popup

The grid management provides a nice function: it is able to render the currently selected item into a popup window. All the item data is available as vertical list within this window – using exactly the same components as are used inside the grid row:

Opening this popup is very simple: you just need to call...

Again you can call the popup via an API and add own functions: FIXGRIDBinding.getRowDataUI().openPopup(...) will pass you back the popup window, so that you e.g. can update its size or position according to your requirements.

Grid Popup Menu

The grid's popup menu processing (“right mouse click menu”) is based on the default popup menu processing of CaptainCasa Enterprise Client: you need to define a POPUPMENU with a certain id within your page. This POPUPENU then is referenced by the grid components.

There are three possibilities to define a popup menu within a grid definition:

FIXGRID ...

    GRIDCOL text=”...” ...

        FIELD text=”...” popupenu=”...” actionListener=”.{...}” ...

 

FIXGRID ... rowpopupmenu=”...”

 

FIXGRID ... popupmenu=”...”

 

A typical usage scenario for a grid is to use FIXGRID-ROWPOPUPMENU and FIXGRID-POPUPMENU in parallel:

Typically the ROWPOPUPMENU definition (i.e. contained menu items) includes the POPUPMENU definition: if the grid is fully occupied with line items, there is no empty space left, and as consequence there is no way for the user to reach the POPUPMENU items if not defined in the ROWPOPUPMENU definition as well.

Saving and Restoring a Grid's Runtime State

During runtime a user may update the grid in various ways:

A common function that is required is to save this information and to restore it if the user revisits the grid.

Of course you can implement such function straight on your own by just using the grid's API. But there is a shortcut to simplify the default scenarios – which is activated by assigning a PERSISTID to the corresponding grid:

Implicit or explicit Storing

In the FIXGRIDBinding there are two protected methods:

    protected void storePersistentDataImplicitly()

    {

        storePersistentData();

    }

 

    protected void storePersistentData()

    {

        ...

    }

 

Every time a grid's structure is updated, the method “storePersistentDataImplicitly” is called. This method by default directly calls the method “storePersistentData” - which takes the current grid configuration and stores it. This means every time the user e.g. re-arranges the columns of a grid the grid structure Is immediately stored.

If you do not want the storing to be implicitly done, then just override “storepersistentDataImplicitly” and do ...nothing! (Maybe you want to maintain a change indicator or do something else...).

Interface IFIXGRIDPersistence2

The method “storePersitentData()” internally finds an instance of an implementation of the interface IFXRIDPersistence2.

public interface IFIXGRIDPersistence2

{

    public PersistentInfo readPersistentInfo(FacesContext context,

                                             FIXGRIDBinding gridBinding,

                                             String pageName,

                                             String persistId);

    

    public void updatePersistentInfo(FacesContext context,

                                     FIXGRIDBinding gridBinding,

                                     String pageName,

                                     String persistId,

                                     PersistentInfo persistentInfo);

}


The finding is done through the system.xml configuration file:

<fixgrid

    persistence=

         "org.eclnt.jsfserver.util.fixgridpersistence.DefaultFIXGRIDPersistence"

        ...

/>

 

The default class to handle the persistence is “DefaultFIXGRIDPersistence”. This class transforms the current grid configuration into an XML string and stores it within the streamstore.

In many scenarios you just want to use the default persistence and manage the actual persisting of the XML on your own. In this case the only thing you need to do is to override two methods:

    protected String readSerializedPersistentInfo(FacesContext context,

                                                  FIXGRIDBinding gridBinding,

                                                  String pageName,

                                                  String persistId)

    {

        String xml = ....read the XML...

        return xml;

    }

 

    protected void saveSerializedPersistentInfo(FacesContext context,

                                                FIXGRIDBinding gridBinding,

                                                String pageName,

                                                String persistId,

                                                PersistentInfo persistentInfo,

                                                String xml)

    {

        ...store XML...

    }

 

Performance Optimization of Grids

In general you do not have to worry about grid performance – both client and server-side. But, there may come up situations in which you should, such as:

Grid Performance

Before getting into details about how to optimize, first have a look at the performance aspects of grid processing.

The FIXGRID – GRIDCOL – etc. component combination, that forms a grid actually is multiplied out at runtime: for each row of the grid there is a corresponding row, each row containing exactly these components that are defined below the GRIDCOL definition.

While multiplying out the expressions of all components are concatenated correspondingly.

Example: in the FIXGRID definition there is an expression defined in the attribute OBJECTBINDING, e.g. “#{abc.grid}”. This expression points to the FIXGRIDBinding object on server side. Below the GRIDCOL definition there might be a FIELD definition, the TEXT attribute of FIELD pointing to “.{firstName}”. For each row, a FIELD component will be multiplied out, the expressions of the components will be:

<fixgrid objectbinding=”#{abc.grid}”>

</fixgrid>

 

 

Row 1:        #{abc.grid.rows[0].firstName}

Row 2:        #{abc.grid.rows[1].firstName}

Row 3:        #{abc.grid.rows[2].firstName}

etc.

 

During JSF request processing all expressions that are referenced within the current component tree are processed. Now imagine a grid with a SBVISIBLEVALUE of “30” and a column count of “50”: in this case 1500 expressions are evaluated on server side with every request.

Remember: only those rows are processed which are actually visible on client side, i.e. not all rows of the grid are processed, but still this is quite a lot to do.

“Change Index” Optimization

The optimization is quite simple: each grid row explicitly tells if it has changed during a request processing or not. In case it has not changed, the components of the grid row are not processed at all. This means also the corresponding resolution of expressions is not done.

You see: the application on server side now has to “help” the surrounding component framework, by telling that certain components do not need to be processed.

There are two things you need to do in order to optimize the grid processing:

The first part is simple. Just use the constructor of FIXGRIDListBinding or FIXGRIDTreeBinding, that allow to pass along the boolean flag “changeIndexIsSupported”. E.g. for FIXGRIDListBinding call the constructor in the following way:

FIXGRIDListBinding<MyRow> m_grid = new FIXGRIDListBinding(true);

 

The second aspect is something you really need to pay attention to: every time a grid row's content is updated you need to tell the grid line about it. If you do not tell, the component processing will assume that nothing has changed within the data of the grid line. You tell the change of data in the following way:

   FIXGRIDItem row = ...;

   row.getChangeIndex().indicateChange();

 

Please note: you just have to call the indicateChange() method in case that an existing item was updated. You do not need to notify about:

All this is recognized as change internally. (BTW: Calling the “indicateChange()” method too often will never cause an error on server side...)

“Change Index” Optimization Scenarios

Optimization is never a problem in some simple scenarios:

“Data Bag” Optimization

If your grid has a lot of columns (e.g. > 100 columns) then the following optimization makes sense for all read-only values:

Consequence: all the data is picked from the item in one chunk – and transferred to the client in one chunk. The distribution of the data from “data bag” into the corresponding cell components is done on client side.

Example + How to implement:

...

public class GridItem extends FIXGRIDItem implements IDatabagProcider

{

    String i_firstName;

    String i_lastName;

    String i_databag;

    public String getDatabag()

    {

        if (i_databag == null)

        {

            Map<String,String> m = new HashMap<String,String>();

            m.put(“firstName”,i_firstName);

            m.put(“lastName”,i_lastName);

            i_databag = ValueManager.encodeComplexValue(m);

        }

        return i_databag;

    }

    public String getFirstName() { ... } // required for sorting!

    public String getLastName() { ... } // required for sorting!

}

 

...

FIXGRID ...

    GRIDCOL SORTREFERENCE=”.{firstName}” ...

        LABEL TEXT=”@cc_db:firstName@” ...

    GRIDCOL SORTREFERENCE=”.{lastName}”...

        LABEL TEXT=”@cc_db:lastName@” ...

...

 

That's it!

Typically the “data bag” optimization is used in grids which are generated using (ROW)DYNAMICCONTENTBINDING. There is a corresponding example in the demo workplace (search for text “Grid Performance”) in which the “data bag” generation is demonstrated.

Please pay attention:

REPEAT Component

In the previous chapter you got to know the FIXGRID component as one way of rendering information, in which you have a collection of items to be presented to the user.

The advantage of the FIXGRID component: it allows a powerful and flexible handling of cell-oriented grids. The disadvantage: if you want to present the item information to the user in some more heterogeneous way, then things are a bit more difficult. - In the FIXGRID component cells are primarily sized by the grid processing (“outside-in sizing”). And in the FIXGRID you think in homogeneous cells.

As consequence there is an alternative: the REPEAT component. This component allows a flexible way of presenting collection items to the user, by just repeating a defined sequence of components for each item of the collection.

Usage of the REPEAT Component

The usage of the REPEAT component is very simple. Take a look onto the following page:



When selecting the first 2 items and pressing “Remove selected items” the screen looks like:



The layout definition is:

<t:row id="g_2">

    <t:pane id="g_3" border="#00000030" padding="10">

        <t:repeat id="g_4" listbinding="#{d.DemoRepeat.items}">

            <t:rowdistance id="g_5" height="5" />

            <t:row id="g_6" coldistance="5">

                <t:checkbox id="g_7" selected=".{selected}" />

                <t:coldistance id="g_8" width="5" />

                <t:field id="g_9" text=".{firstName}" width="100" />

                <t:field id="g_10" text=".{lastName}" width="100" />

            </t:row>

            <t:rowdistance id="g_11" height="5" />

        </t:repeat>

    </t:pane>

</t:row>

<t:rowdistance id="g_12" height="10" />

<t:row id="g_13" coldistance="5">

    <t:button id="g_14"

        actionListener="#{d.DemoRepeat.onAddItemAction}"

        text="Add one item" />

    <t:button id="g_15"

        actionListener="#{d.DemoRepeat.onRemoveItemsAction}"

        text="Remove selected items" />

    <t:button id="g_16"

        actionListener="#{d.DemoRepeat.onUpdateItemsAction}"

        text="Update items" />

</t:row>

 

You see:

This means the content of the REPEAT component is repeated for each single item of the list that is bound via the attribute REPEAT-LISTBINDING. Inside the REPEAT the properties of the items are reference by “.{...}” expressions.

The server-side Java code is:

package workplace;

 

import java.io.Serializable;

import java.util.ArrayList;

import java.util.List;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.pagebean.PageBean;

import org.eclnt.workplace.IWorkpageDispatcher;

 

import javax.faces.event.ActionEvent;

 

@CCGenClass (expressionBase="#{d.DemoRepeat}")

 

public class DemoRepeat

    extends DemoBasePageBean

    implements Serializable

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

    

    public class Item

    {

        boolean m_selected = false;

        String i_firstName;

        String i_lastName;

        public String getFirstName() { return i_firstName; }

        public void setFirstName(String firstName) { i_firstName = firstName; }

        public String getLastName() { return i_lastName; }

        public void setLastName(String lastName) { i_lastName = lastName; }

        public boolean getSelected() { return m_selected; }

        public void setSelected(boolean value) { this.m_selected = value; }

 

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

 

    List<Item> m_items = new ArrayList<Item>();

    

    // ------------------------------------------------------------------------

    // constructors & initialization

    // ------------------------------------------------------------------------

 

    public DemoRepeat(IWorkpageDispatcher dispatcher)

    {

        super(dispatcher);

        for (int i=0; i<5; i++)

        {

            Item it = new Item();

            it.i_firstName = "First " + i;

            it.i_lastName = "Last " + i;

            m_items.add(it);

        }

    }

 

    public String getPageName() { return "/workplace/demorepeat.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.DemoRepeat}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

 

    public List<Item> getItems() { return m_items; }

    

    public void onAddItemAction(ActionEvent event)

    {

        Item it = new Item();

        it.i_firstName = "New item";

        it.i_lastName = "New item";

        m_items.add(it);

    }

 

    public void onRemoveItemsAction(ActionEvent event)

    {

        for (int i=m_items.size()-1; i>=0; i--)

        {

            Item item = m_items.get(i);

            if (item.getSelected() == true)

            {

                m_items.remove(i);

            }

        }

    }

 

    public void onUpdateItemsAction(ActionEvent event)

    {

        for (Item item: m_items)

        {

            item.i_firstName += "#";

            item.i_lastName += "#";

        }

    }

 

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

}

 

The list of items (m_items) that is used for the REPEAT processing is just a normal instance of java.util.List – here it is an ArrayList. The items' class is an inner class in the example – but may be a “normal” class as well, of course.

By updating the items – either the number of items or the content of the items – the REPEAT component automatically follows and adapts the rendering accordingly.

Nesting REPEAT Components

It is possible to nest one REPEAT component inside an other. Please check the corresponding example within the Demo Workplace:



The principle behind is simple:

In principal there is no limit of nesting.

Performance Considerations

In general

The REPEAT component binds to a list and renders the content of the list by repeating its content correspondingly. Of course this means that the component has a certain performance impact if it comes to lists with a lot of items:

As consequence you should take care to not send too big lists into the REPEAT component processing. It's difficult to exactly define some limit, but in general you should think about performance aspects if the list has more than 50 items.

Server Side Scrolling

The solution what to do with long lists is that you need to split the whole list into partitions and allow the user to navigate between. - You need some indirection between the original, long list and the list that is presented to the user.

This is exactly what the class “ServerSideScrollList” does. Please take a look at the example that is part of the Demo Workplace:



From Layout Definition point of view the REPEAT part looks “just normal”:

<t:pane id="g_3" border="#00000030" padding="10">

    <t:row id="g_4">

        <t:label id="g_5" font="weight:bold" text="First Name"

            width="100" />

        <t:label id="g_6" font="weight:bold" text="Last Name" width="100" />

    </t:row>

    <t:rowdistance id="g_7" height="10" />

    <t:repeat id="g_8"

        listbinding="#{d.DemoRepeatServerSideScrolling.items}">

        <t:row id="g_9">

            <t:label id="g_10" text=".{firstName}" width="100" />

            <t:label id="g_11" text=".{lastName}" width="100" />

        </t:row>

        <t:rowdistance id="g_12" height="3" />

        <t:rowline id="g_13" />

    </t:repeat>

</t:pane>

 

The REPEAT binds to some “items” list on the server side.

But looking into the code you will see that instead of using a plain List-implementation a special class “ServerSideScrollList” is used:

    public class Item

    {

        String m_firstName;

        String m_lastName;

        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; }

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    ServerSideScrollList<Item> m_items = new ServerSideScrollList<Item>();

    

    // ------------------------------------------------------------------------

    // constructors & initialization

    // ------------------------------------------------------------------------

 

    public DemoRepeatServerSideScrolling(IWorkpageDispatcher dispatcher)

    {

        super(dispatcher);

        for (int i=0; i<50; i++)

        {

            Item it = new Item();

            it.setFirstName("First " + i);

            it.setLastName("Last " + i);

            m_items.getItems().add(it);

        }

    }

 

    public String getPageName() { return "/workplace/demorepeatserversidescrolling.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.DemoRepeatServerSideScrolling}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

 

    public ServerSideScrollList<Item> getItems() { return m_items; }

 

The items are not directly accessed in “m_items”, but are accessed by calling the method “getItems()”.

From the screenshot above you see that not 50 items are rendered within the client – but only 10. This is due to the default behavior of the ServerSideScrollList – separating all the items in partitions of 10 items.

The class ServerSideScrollList allows you to:

Please check the JavaDoc API documentation of this class.

In the example, next to the PANE holding the REPEAT processing there is the definition of a SCROLLBAR component:

<t:row id="g_2">

    <t:pane id="g_3" border="#00000030" padding="10">

       ...

        <t:repeat id="g_8"

            listbinding="#{d.DemoRepeatServerSideScrolling.items}">

           ...

        </t:repeat>

       ...

    </t:pane>

    <t:scrollbar id="g_14" flush="true" height="-1000"

        orientation="vertical"

        sbmaxvalue="#{d.DemoRepeatServerSideScrolling.items.maxIndex}"

        sbminvalue="0"

        sbsize="#{d.DemoRepeatServerSideScrolling.items.numberOfItems}"

        value="#{d.DemoRepeatServerSideScrolling.items.topIndex}" />

</t:row>

 

This SCROLLBAR component binds to the properties that are provided by the ServerSideScrollList. When moving the scroll bar then the corresponding top index of the ServerSideScrollList instance is set – and the REPEAT content is scrolled correspondingly.

Page Navigation / Combining Dialogs

Basics

CaptainCasa Enterprise Client provides the following possibilities of navigation:

Of course all possibilities can be mixes with one another.

This chapter covers the basic aspects of page navigation and page modularization. Please also check the chapter “Page Bean Modularization” - which is a very useful concept built on top.

JSF Navigation Concepts

You may already know something about JSF navigation concepts: these are based on so called “action” definitions, which then refer to definitions inside faces-config.xml. Being a quite nice way to define page sequences, the JSF navigation concepts are not able to manage complex (but typical!) scenarios of typical rich client user interfaces, including:

For these reasons we do not internally use and support the JSF navigation concepts.

Enteprise Client Concepts

CaptainCasa Enterprise Client offers two ways of managing navigation:

...what's about page sequences? Well, page sequences are just a special usage type for nesting pages – in which the outer page updates the inner page following certain application rules.

Page Inclusion vs. Page Bean Inclusion

We need to take a look back into the chronology of Enterprise Client. ...Sorry!

The first possibility of including one page within another page was the “page inclusion” (component ROWINCLUDE). The outer page (e.g. “outer.jsp”) directly defines an area, in which an inner page (e.g. “inner.jsp”) is running.

++++++++++++++++++++++++++++++++++++++

+ outer.jsp                          +

+                                    +

+ ...#{d.OuterUI.xxx}                +

+                                    +

+                                    +

+   ++++++++++++++++++++++++++       +

+   + inner.jsp              +       +

+   +                        +       +

+   + ...#{d.InnerUI.xxx}... +       +

+   ++++++++++++++++++++++++++       +

                                     +

++++++++++++++++++++++++++++++++++++++

 

Well, this concept is very simple on the one hand – because the result is something like a (virtual) big XML-file – merged out of the outer and the inner page's xml. So, where are the difficulties?

The difficulties are that within the page definition there are expressions. E.g. the inner page contains an expression “#{d.InnerUI.firstName}”. The expressions are taken over when the inner page is nested by the outer page. So the virtual big page consists out of expressions addressing the outer page (e.g. #{d.OuterUI.user}”) and addressing the inner page (“#{d.InnerUI.firstName}”). On server side this means that two instances of beans are addressed through the “d”-Dispatcher, which are not linked in any way – other than sharing the same dispatcher instance.

Let's add one more level of complexity: now the outer page not only wants to show one inner page, but it wants to show the inner page twice – e.g. on the left and on the right side. Of course filled with different data!

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+ outer.jsp                                                    +

+                                                              +

+ ...#{d.OuterUI.xxx}                                          +

+                                                              +

+                                                              +

+   ++++++++++++++++++++++++++   ++++++++++++++++++++++++++    +

+   + inner.jsp              +   + inner.jsp              +    +

+   +                        +   +                        +    +

+   + ...#{d.InnerUI.xxx}... +   + ...#{d.InnerUI.xxx}... +    +

+   ++++++++++++++++++++++++++   ++++++++++++++++++++++++++    +

                                                               +

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

And now, the static taking over of expressions while building the virtual big page does not work anymore. The left inner page and the right inner page are referencing the same expression – and this means they are referencing the same object instance on server side!

Consequence: you can by using “page inclusion” embed one page twice (or more), but the data shown in the page is always the same.

 

This is the reason why an alternative to page inclusion was added to the Enterprise Client: the “Page Bean Inclusion”.

When thinking in Page Beans then basically you think in UI objects – and not in jsp-layouts anymore. The UI objects are called “page beans”. Page beans are just normal server side objects, which are derived from the abstract class “PageBean”. When following the tutorial you already created page bean classes – so there is nothing special about.

Let's immediately check the complex example from above and show how it is built using page beans:

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+ OuterUI instance                                             +

+ ...using: outer.jsp                                          +

+                                                              +

+                                                              +

+   OuterUI.getLeft()            OuterUI.getRight()            +

+   ++++++++++++++++++++++++++   ++++++++++++++++++++++++++    +

+   + InnerUI instance       +   + InnerUI instance       +    +

+   + ...using: inner.jsp    +   + ...using: inner.jsp    +    +

+   + ...#{d.InnerUI.xxx}... +   + ...#{d.InnerUI.xxx}... +    +

+   ++++++++++++++++++++++++++   ++++++++++++++++++++++++++    +

                                                               +

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

Now there is an outer instance of “OuterUI” which internally holds two instances of “InnerUI”. The instances are available through the properties “left” and “right”. The pseudo code of the OuterUI class is:

public class OuterUI

{

    InnerUI m_left = new InnerUI();

    InnerUI m_right = new InnerUI();

  

    public InnerUI getLeft() { return m_left; }

    public InnerUI getRight() { return m_right; }

}

 

You already see:

Consequeces

In the meantime we definitely recommend to only use the page bean way for any type of navigation. It's just much simpler to use and it's covering any complexity when it comes to nesting pages into one another.

Of course the page inclusion still is available, but: only use it if you have a real reason. The default strategy of your navigation implementation should always be page bean based.

This means:

Page Bean Modularization - Example

Page

This is the screenshot of the example:

There is an “outside” page containing two “inside” pages, both sharing the same jsp-page, but containing different instance data. When pressing the “Open Vacation Address” button then a third instance of the address is shown in a popup window.

Java Code

Let's take a look onto the code of the outside page first:

package workplace;

 

...

 

public class DemoPageBeanUI

    extends WorkpageDispatchedPageBean

    implements Serializable

{

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

 

    protected String m_lastName;

    protected String m_firstName;

    

    DemoPageBeanAddressUI m_homeAddress = new DemoPageBeanAddressUI();

    DemoPageBeanAddressUI m_businessAddress = new DemoPageBeanAddressUI();

    DemoPageBeanAddressUI m_vacationAddress = new DemoPageBeanAddressUI();

    

    ModalPopup m_popup;

    

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    public DemoPageBeanUI(IWorkpageDispatcher dispatcher)

    {

        super(dispatcher);

        initPageBeans();

    }

 

    public String getPageName() { return "/workplace/demopagebean.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.DemoPageBeanUI}"; }

    

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

 

    public DemoPageBeanAddressUI getHomeAddress() { return m_homeAddress; }

    public DemoPageBeanAddressUI getBusinessAddress() { return m_businessAddress; }

    

    public String getLastName() { return m_lastName; }

    public void setLastName(String value) { m_lastName = value; }

 

    public String getFirstName() { return m_firstName; }

    public void setFirstName(String value) { m_firstName = value; }

 

    public void onOpenVacationAddress(ActionEvent event)

    {

        openModelessPopup(m_vacationAddress,"Vacation Address",0,0,new ModelessPopup.IModelessPopupListener()

        {

            public void reactOnPopupClosedByUser()

            {

                closePopup(m_vacationAddress);

            }

        });

    }

    

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

 

    private void initPageBeans()

    {

        m_homeAddress.setTitle("Home Address");

        m_homeAddress.setStreet1("Home Street 1");

        m_homeAddress.setStreet2("Home Street 2");

        m_businessAddress.setTitle("Business Address");

        m_businessAddress.setStreet1("Business Street 1");

        m_businessAddress.setStreet2("Business Street 2");

        m_vacationAddress.setTitle("Vacation Address");

        m_vacationAddress.setStreet1("Vacation Street 1");

        m_vacationAddress.setStreet2("Vacation Street 2");

    }

    

}

 

Each contained page is represented by a corresponding page instance (m_homeAddress, m_businessAddress and m_vacationAddress), with corresponding get-methods for accessing the instances. The sub-pages are members / properties of the outside page.

In addition you see that there are two special methods “getPageName()” and “getRootExpressionUsedInPage()” that are implemented.

The code of the contained instances is:

package workplace;

 

...

 

public class DemoPageBeanAddressUI

    extends PageBean

    implements Serializable

{

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

 

    protected String m_title = "<Title>";

    protected String m_street1 = "<Street1>";

    protected String m_street2 = "<Street2>";

    

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

 

    public String getPageName() { return "/workplace/demopagebeanaddress.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.DemoPageBeanAddressUI}"; }

    

    public String getStreet1() { return m_street1; }

    public void setStreet1(String value) { m_street1 = value; }

    

    public String getStreet2() { return m_street2; }

    public void setStreet2(String value) { m_street2 = value; }

 

    public String getTitle() { return m_title; }

    public void setTitle(String value) { m_title = value; }

 

}

 

Again the code contains an implementation of the “getPageName()” and “getRootExpressionUsedInPage()”.

JSP Page

Now, let's take a look onto the outside jsp page definition:

/workplace/demopagebean.jsp:

 

<t:rowheader id="g_1">

  <t:button id="g_2"

        actionListener="#{d.DemoPageBeanUI.onOpenVacationAddress}"

        text="Open Vacation Address" />

</t:rowheader>

<t:rowbodypane id="g_3" rowdistance="5">

  <t:row id="g_4">

    <t:label id="g_5" text="First Name" width="100" />

    <t:field id="g_6" text="#{d.DemoPageBeanUI.firstName}" width="100" />

  </t:row>

  <t:row id="g_7">

    <t:label id="g_8" text="Last Name" width="100" />

    <t:field id="g_9" text="#{d.DemoPageBeanUI.lastName}" width="100" />

  </t:row>

  <t:rowdistance id="g_10" height="20" />

  <t:rowpagebeaninclude id="g_11"

        pagebeanbinding="#{d.DemoPageBeanUI.businessAddress}" />

  <t:rowpagebeaninclude id="g_12"

        pagebeanbinding="#{d.DemoPageBeanUI.homeAddress}" />

</t:rowbodypane>

<t:rowstatusbar id="g_13" />

 

The integration of the two address pages into the outside page is not done by using the ROWINCLUDE – but by using the ROWPAGEBEANINCLUDE component.

The jsp page definition of the address page is a “very normal” one:

/workplace/demopagebeanaddress.jsp:

 

<t:row id="g_1">

  <t:pane id="g_2" border="#00000030" padding="0" rowdistance="0">

    <t:rowtitlebar id="g_3" text="#{d.DemoPageBeanAddressUI.title}" />

    <t:row id="g_4">

      <t:pane id="g_5" padding="10" rowdistance="5">

        <t:row id="g_6">

          <t:label id="g_7" text="Street (1)" width="100" />

           <t:field id="g_8" text="#{d.DemoPageBeanAddressUI.street1}"

                        width="100" />

        </t:row>

        <t:row id="g_9">

          <t:label id="g_10" text="Street (2)" width="100" />

          <t:field id="g_11" text="#{d.DemoPageBeanAddressUI.street2}"

                        width="100" />

        </t:row>

      </t:pane>

    </t:row>

  </t:pane>

</t:row>

 

Page Bean Details

The concepts are based on the paradigm that for one page there is one bean serving the page:

Example: the “demopagebeanaddress.jsp” is related to the “DemoPageBeanAddressUI” class – all its content is managed by the class.

IPageBean / PageBean Instances

A bean covering all the aspects of one page is called “PageBean” - and implements the interface “IPageBean”, typically be deriving from the “PageBean” class.

A page bean has to implement an interface that allows the modularization framework to integrate it into other JSP pages – executed by the ROWPAGEBEANINCLUDE component. The two methods that need to be provided are:

The ROWPAGEBEANINCLUDE Component

This is the component to embed a page bean into a page.


Embedding covers two aspects:

Result: there is no additional effort for integrating page bean instances within a page. The page beans instances are normal members / properties of other page beans instances. While the normal ROWINCLUDE component just optically includes a page into another pages, the ROWPAGEBEANINCLUDE component includes a page bean object and internally “rules” all the binding issues between the JSP-page definition and the page bean instance.

What happens internally...

Inside the management of the ROWPAGEBEANINCLUDE component there is an automated update of the expressions of an included page.

Let's assume the following scenario:

Page Bean Navigation

...this is the typical question: “How do I switch from one page bean to the next?”.

The answer is: pages and page beans are independent objects and can not themselves switch to the next page. As consequence, navigation needs to be done through an outside page, that holds an inside page – and that exchanges the inside page.

This may sound complex, but indeed is not. Let's take a look onto the following example:



When pressing the “Next Page” button then the screen changes to:



The outside page is defined in the following way:

...

...

    <t:row id="g_2">

        <t:pane id="g_3" height="100%" width="100%">

            <t:rowpagebeaninclude id="g_4"

        pagebeanbinding="#{d.DemoPageBeanNavOutside.currentStep}" />

        </t:pane>

    </t:row>

    <t:row id="g_5">

        <t:coldistance id="g_6" width="100%" />

        <t:button id="g_7"

            actionListener="#{d.DemoPageBeanNavOutside.onPrevious}"

            enabled="#{d.DemoPageBeanNavOutside.previousEnabled}"

            text="Previous Page" width="100" />

        <t:coldistance id="g_8" width="5" />

        <t:button id="g_9"

            actionListener="#{d.DemoPageBeanNavOutside.onNext}"

            enabled="#{d.DemoPageBeanNavOutside.nextEnabled}"

            text="Next Page" width="100" />

    </t:row>

...

 

There is a ROWPAGEBEANINCLUDE definition, pointing via its expression to the “currentStep” property of the Java program:

public class DemoPageBeanNavOutside

    extends DemoBase

    implements Serializable

{

    int m_currentStepIndex = 0;

    List<IPageBean> m_steps = new ArrayList<IPageBean>();

    

    public DemoPageBeanNavOutside(IWorkpageDispatcher workpageDispatcher)

    {

        super(workpageDispatcher);        

        m_steps.add(new DemoPageBeanNavStep1());

        m_steps.add(new DemoPageBeanNavStep2());

    }

    

    public String getPageName() { return "/workplace/demopagebeannavoutside.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.DemoPageBeanNavOutside}"; }

    

    public IPageBean getCurrentStep() { return m_steps.get(m_currentStepIndex); }

 

    public boolean isPreviousEnabled() { return m_currentStepIndex > 0 ? true: false; }

    public boolean isNextEnabled() { return m_currentStepIndex < (m_steps.size()-1) ? true: false; }

    

    public void onPrevious(ActionEvent event)

    {

        if (m_currentStepIndex > 0)

            m_currentStepIndex--;

    }

    

    public void onNext(ActionEvent event)

    {

        if (m_currentStepIndex < (m_steps.size()-1))

            m_currentStepIndex++;

    }

 

}

 

The “currentStep” property passes back a page bean instance, that is selected dependent from an index, that is updated when the user presses the “Next” or the “Previous” button.

The contained page beans are straight and simple page bean implementations, e.g. the first step's bean and layout is:

<t:rowbodypane id="g_1" rowdistance="5">

    <t:row id="g_2">

        <t:label id="g_3" font="size:16" text="Define your name:" />

    </t:row>

    <t:rowdistance id="g_4" height="20" />

    <t:row id="g_5">

        <t:label id="g_6" text="First name" width="100" />

        <t:field id="g_7" text="#{d.DemoPageBeanNavStep1.firstName}"

            width="200" />

    </t:row>

    <t:row id="g_8">

        <t:label id="g_9" text="Last name" width="100" />

        <t:field id="g_10" text="#{d.DemoPageBeanNavStep1.lastName}"

            width="200" />

    </t:row>

</t:rowbodypane>

 

 

public class DemoPageBeanNavStep1

    extends PageBean

    implements Serializable

{

    protected String m_firstName;

    protected String m_lastName;

 

    public DemoPageBeanNavStep1()

    {

    }

    

    public String getPageName() { return "/workplace/demopagebeannavstep1.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.DemoPageBeanNavStep1}"; }

    

    public String getFirstName() { return m_firstName; }

    public void setFirstName(String value) { m_firstName = value; }

    

    public String getLastName() { return m_lastName; }

    public void setLastName(String value) { m_lastName = value; }

 

}

 

In the example the navigation is done through the outside page – by pressing corresponding buttons. Of course the navigation could also be triggered from the inside page – in this case you can just set up any interface relation (in means of simple Java-API) between the outside page and the inside page(s).

Popup Management

Each page bean instance inherits the following methods for opening and closing popup windows:

The open-methods return back an instance of ModalPopup / ModelessPopup.

The following example shows a page bean, opening up an other page bean:

The page bean that is called as popup is exactly the same one as used in the previous example, explaining navigation concepts. So, let's focus onto the page that opens up the popup.

The layout and code are:

<t:row id="g_2">

    <t:button id="g_3"

        actionListener="#{d.DemoPageBeanPopupCaller.onOpenPageBeanInPopup}"

        text="Open Page Bean in Popup" />

</t:row>

 

 

 

 

public class DemoPageBeanPopupCaller

    extends WorkpageDispatchedPageBean

    implements Serializable

{

    public DemoPageBeanPopupCaller(IWorkpageDispatcher workpageDispatcher)

    {

        super(workpageDispatcher);        

    }

    

    public String getPageName() { return "/workplace/demopagebeanpopupcaller.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.DemoPageBeanPopupCaller}"; }

    

    public void onOpenPageBeanInPopup(ActionEvent event)

    {

        // create page to be opened in popup

        final DemoPageBeanAddress address = new DemoPageBeanAddress();

        address.setTitle("nice title");

        address.setStreet1("nice street 1");

        address.setStreet2("nice street 2");

        // open popup (width & height are set to 0, so that popup is sized

        // by its content

        final ModalPopup mp = openModalPopup(address,

                                             "Title of popup",0,0,null);

        mp.setPopupListener(new ModalPopup.IModalPopupListener()

        {

            // This method is called when the user explicitly

            // closes the popup by alt-F4 or by clicking

            // onto the right-top close-icon

            public void reactOnPopupClosedByUser()

            {

                closePopup(address);

                Statusbar.outputMessage("Modal popup was closed!");

            }

        });

    }

 

}

 

Things are quite simple: the page bean is created and configured. It is passed into the openModalPopup-methods – that internally is inherited from the page-bean-related base classes.

Please pay attention: when opening a popup dialog, then opening is always the “easy part”. But you always have to keep in mind, that all navigation is controlled by your server side program – including the closing of a popup. A popup does not close itself, e.g. if the user presses alt-F4 or when the user clicks the close-icon of the popup. This only internally creates a close-request that is delegated to a listener, which then decides to actually close the popup.

Consequence: implementing the popup listeners is very important, otherwise popups are not close-able!

Page Bean Patterns

Please note that there is a documentation “Page Bean Patterns” available within the documentation site http://captaincasa.org/documentation. In this documentation you find practical examples, showing how to organize the communication between page beans efficiently.

Default Page Bean Classes

When creating a layout definition within the CaptainCasa toolset using the following popup...

...then some default code is generated that is useful when working with page beans.

package workplace;

 

import java.io.Serializable;

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.pagebean.PageBean;

 

import javax.faces.event.ActionEvent;

 

@CCGenClass (expressionBase="#{d.SomePageBeanUI}")

 

public class SomePageBeanUI

    extends PageBean

    implements Serializable

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

    

    /* Listener to the user of the page bean. */

    public interface IListener

    {

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    private IListener m_listener;

    

    // ------------------------------------------------------------------------

    // constructors & initialization

    // ------------------------------------------------------------------------

 

    public SomePageBeanUI()

    {

    }

 

    public String getPageName()

    {

        return "/workplace/demoactivextest.jsp";

    }

    public String getRootExpressionUsedInPage()

    {

        return "#{d.SomePageBeanUI}";

    }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

 

    /* Initialization of the bean. Add any parameter that is required within

       your scenario. */

    public void prepare(IListener listener)

    {

        m_listener = listener;

    }

 

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

}

 

A page bean is some class (at runtime: object) which is the 1:1 counter part of a visible page/area. Page beans should be defined as autarc modules which are easily embed-able into other page beans.

For this reason the following code is generated:

In short words: the page bean is an object in which there is a defined way to pass parameters in (“prepare”) and in which there is a defined way of reporting inner events back to the caller (“IListener”).

Example

The generically usable page bean...

Let's assume we require a page bean to select an “id” from a list of “id/text” items:

The page bean should be usable e.g. within a popup dialog in a generic way.

The layout is a simple grid with two buttons below:

<t:rowbodypane id="g_1" padding="0" stylevariant="cc_directcontent">

    <t:row id="g_2">

        <t:fixgrid id="g_3" height="100%"

            objectbinding="#{d.DemoPagebeanObjectSelection.grid}"

            sbvisibleamount="40" width="100%">

            <t:gridcol id="g_4" text="Id" width="100">

                <t:label id="g_5" text=".{id}" />

            </t:gridcol>

            <t:gridcol id="g_6" text="Text" width="100%">

                <t:label id="g_7" text=".{text}" />

            </t:gridcol>

        </t:fixgrid>

    </t:row>

</t:rowbodypane>

<t:rowheader id="g_8">

    <t:button id="g_9"

        actionListener="#{d.DemoPagebeanObjectSelection.onSelectAction}"

        text="Select" width="100+" />

    <t:coldistance id="g_10" width="100%" />

    <t:button id="g_11"

        actionListener="#{d.DemoPagebeanObjectSelection.onCancelAction}"

        text="Cancel" width="100+" />

</t:rowheader>

 

The code is:

package workplace;

 

import java.io.Serializable;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.elements.impl.FIXGRIDItem;

import org.eclnt.jsfserver.elements.impl.FIXGRIDListBinding;

import org.eclnt.jsfserver.pagebean.PageBean;

 

@CCGenClass (expressionBase="#{d.DemoPagebeanObjectSelection}")

public class DemoPagebeanObjectSelection

    extends PageBean

    implements Serializable

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

    

    public interface IListener

    {

        void reactOnSelection(String id);

        void reactOnCancel();

    }

    

    public class GridItem extends FIXGRIDItem implements java.io.Serializable

    {

        String i_id;

        String i_text;

        private GridItem(String id, String text)

        {

            i_id = id;

            i_text = text;

        }

        public String getText() { return i_text; }

        public String getId() { return i_id; }

        public void onRowExecute()

        {

            if (m_listener != null) m_listener.reactOnSelection(i_id);

        }

    }

 

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    private IListener m_listener;

    FIXGRIDListBinding<GridItem> m_grid = new FIXGRIDListBinding<GridItem>();

    

    // ------------------------------------------------------------------------

    // constructors & initialization

    // ------------------------------------------------------------------------

 

    public DemoPagebeanObjectSelection()

    {

    }

 

    public String getPageName()

    {

        return "/workplace/demopagebeanobjectselection.jsp";

    }

    public String getRootExpressionUsedInPage()

    {

        return "#{d.DemoPagebeanObjectSelection}";

    }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

 

    public void prepare(String[] ids, String[] texts,

                        IListener listener)

    {

        m_listener = listener;

        // fill grid

        m_grid.getItems().clear();

        for (int i=0; i<ids.length; i++)

            m_grid.getItems().add(new GridItem(ids[i],texts[i]));

    }

 

    public FIXGRIDListBinding<GridItem> getGrid() { return m_grid; }

 

    public void onCancelAction(javax.faces.event.ActionEvent event)

    {

        if (m_listener != null) m_listener.reactOnCancel();

    }

 

    public void onSelectAction(javax.faces.event.ActionEvent event)

    {

        GridItem gi = m_grid.getSelectedItem();

        if (gi != null)

        {

            if (m_listener != null) m_listener.reactOnSelection(gi.i_id);

        }

    }

}

 

The highlighted code areas show:

The outside bean...

Now let's embed this generic bean into some concrete popup navigation of an outside bean:

The caller's layout definition is simple:

<t:rowtitlebar id="g_2" text="Outisde page bean" />

<t:rowbodypane id="g_3">

    <t:row id="g_4">

        <t:box id="g_5" width="100%">

            <t:row id="g_6">

                <t:label id="g_7" text="Department" width="100+" />

                <t:field id="g_8" placeholder="Department Id"

                    text="#{d.DemoPageBeanOutside.department}"

                               width="200" />

                <t:coldistance id="g_9" width="100%;10" />

                <t:button id="g_10"

                    actionListener="#{d.DemoPageBeanOutside.onDepartmentSelectAction}"

                    text="Select..." />

            </t:row>

        </t:box>

    </t:row>

</t:rowbodypane>

<t:rowstatusbar id="g_11" />

 

When ther user presses the “Select...”-button, the outside bean creates the page bean that is popped up, prepares it and defines the reaction on the page bean's call-backs/events:

package workplace;

 

import java.io.Serializable;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.elements.util.DefaultModalPopupListener;

import org.eclnt.jsfserver.pagebean.PageBean;

 

@CCGenClass (expressionBase="#{d.DemoPageBeanOutside}")

 

public class DemoPageBeanOutside

    extends PageBean

    implements Serializable

{

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    String m_department;

    

    // ------------------------------------------------------------------------

    // constructors & initialization

    // ------------------------------------------------------------------------

 

    public DemoPageBeanOutside()

    {

    }

 

    public String getPageName() { return "/workplace/demopagebeanoutside.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.DemoPageBeanOutside}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

 

    public String getDepartment() { return m_department; }

    public void setDepartment(String value) { this.m_department = value; }

 

    public void onDepartmentSelectAction(javax.faces.event.ActionEvent event)

    {

        final DemoPagebeanObjectSelection os =

            new DemoPagebeanObjectSelection();

        String[] ids = new String[] {"0001","0002","0003"};

        String[] texts = new String[] {"Sales","Production","Management"};

        os.prepare(ids,texts,new DemoPagebeanObjectSelection.IListener()

        {

            @Override

            public void reactOnSelection(String id)

            {

                m_department = id;

                closePopup(os);

            }

            @Override

            public void reactOnCancel()

            {

                closePopup(os);

            }

        });

        openModalPopup(os,"Department selection",400,600,

                       new DefaultModalPopupListener(this,os));

    }

}

 

The implementation of the call-back methods is done using inner classes. You could of course also use explicit classes.

Conclusion I

The page bean for managing the list is generically usable. It is not bound to a selection of departments or any other specific entity – but is selecting one id out of several ids which are passed. The page bean does not know if it is called as part of a screen (via ROWPAGEBEANINCLUDE) or if is called within a popup-dialog.

As consequence the page bean is not depending on any outside dependencies – and is generically usable as consequence.

Conclusion II

The interface between the outside bean and the inside bean is a pure Java-API (prepare(...), IListener...). We propose to define the API in the way described in the previous text – but you may design your own API of course!

Example: in out proposal there is only one listener instance within a page bean to the caller:

IListener m_listener;

 

public void prepare(...., IListener listener)
{

    ....

}

 

Maybe in your environment you may require multiple listeners:

Set<IListener> m_listeners = new HashSet<IListener>();

 

public void addListener(IListener listener) { .. }
public void removeListener(IListener listener) { .. }

 

You may define your own base class extending PageBean, which you use for your page beans:

public class XYZBasePageBean extennds PageBean

{

    Set<IListener> m_listeners = new HashSet<IListener>();

 

    public void addListener(IListener listener) { .. }
   public void removeListener(IListener listener) { .. }

}

 

 

public class MyPageBean extends XYZPageBean

{

    ...

}

 

In other words: feel free to use your Java knowledge on server side – there are no restrictions from CaptainCasa point of view.

Page Beans with Parent Content Areas

So far a page bean was described as a dialog definition that can be easily embedded into a page and/or opened as a popup dialog. In case of using the page bean in a page you e.g. use the ROWPAGEBEANINCLUDE component to include the page bean dialog into the current dialog:

...

    <t:pane ...>

        <t:rowpagebeaninclude beanbinding=”...” .../>

    </t:pane>

...

 

The page bean is – in this case – the leaf node of the XML layout definition.

There are cases in which you want to define the page bean not as “end level” of your layout definition – but you want to define it as “interim level” - so that the page bean takes over some “standardized framing functions” for some content – without the content being explicit part of the page bean.

Example

The following scenario consists out of a page bean that is used to open up and manage a scale-able area. The content of the area is up to the user of the page bean:

The layout definition of the complete scenario is:

<t:rowdemobodypane id="g_2" objectbinding="#{d.DemoPBWithExitUser}">

    <t:rowpagebeaninclude id="g_3"

        pagebeanbinding="#{d.DemoPBWithExitUser.content}"

        shownullcontent="true">

        <t:pane id="g_4" padding="0" exitid=”BODY”>

            <t:rowheader id="g_5">

                <t:button id="g_6"

                    actionListener="#{d.DemoPBWithExitUser.onAction}"

                    text="Update names" />

            </t:rowheader>

            <t:row id="g_7">

                <t:foldablepane id="g_8" text="Inner pane" width="100%">

                    <t:row id="g_9">

                        <t:field id="g_10" labeltext="Vorname"

                            text="#{d.DemoPBWithExitUser.firstName}" width="100%" />

                    </t:row>

                    <t:row id="g_11">

                        <t:field id="g_12" labeltext="Nachname"

                            text="#{d.DemoPBWithExitUser.lastName}" width="100%" />

                    </t:row>

                </t:foldablepane>

            </t:row>

        </t:pane>

    </t:rowpagebeaninclude>

</t:rowdemobodypane>

 

You see the layout definition does not end with the ROWPAGEBEANINCLUDE definition but continues with a PANE that is placed below. The content of the PANE is exactly the one that is the inner part of the scale-able area.

Defining corresponding Page Beans: the PARENTEXIT Component

Let's continue with the example: where does the included page bean know from where to put the content of the using dialog?

The layout definition of the page bean is:

<t:row id="g_2">

    <t:pane id="g_3" border="#00000030" height="100%" padding="1"

        width="100%">

        <t:rowheader id="g_4">

            <t:coldistance id="g_5" width="100%" />

            <t:slider id="g_6"

                actionListener="#{d.DemoPBWithExit.onScale100Flush}" flush="true"

                flushtimer="500" majortickspacing="50" maxvalue="200"

                minortickspacing="10" minvalue="50"

                value="#{d.DemoPBWithExit.scale100}" width="300" />

            <t:coldistance id="g_7" width="100%" />

        </t:rowheader>

        <t:row id="g_8">

            <t:scrollpane id="g_9" background="#FFFFFF" height="100%"

                width="100%">

                <t:rowline id="g_10" />

                <t:row id="g_11">

                    <t:scalepane id="g_12" flush="true" height="100%"

                        scale="#{d.DemoPBWithExit.scale1}" width="100%"

                        withuserscaling="true">

                        <t:parentexit id="g_13" exitid=”BODY”/>

                    </t:scalepane>

                </t:row>

            </t:scrollpane>

        </t:row>

    </t:pane>

</t:row>

 

Inside the layout definition of the page bean you see a special component: the PARENTEXIT component. This is the place where the page bean opens up a content area in which the layout content of the using page is embedded.

It's important to point out that the layout content of the using page still runs in the context of the using dialog – despite now optically running within the layout of the page bean.

Multiple PARENTEXIT Components

In the example above the page bean opened up one area to place parent content by defining a PARENTEXIT component.

A page bean can also open up multiple area by just defining several PARENTEXIT components inside. Each PARENTEXIT definition must define an own EXITID value, which needs to be unique within the page definition.

On the other side the user of the page bean must explicitly define the EXITID of the component which is to be placed into the corresponding area.

Extending Page Beans

When creating a page bean class extending another page bean class there are two aspects of extensions:

There is no specific issue when extending the Java code – page bean classes are normal Java classes, which you can inherit from one another.

Let's take a closer look onto the layouting level of a page bean: this layouting level is defined by the methods...:

public String getPageName() { return ...; }

public String getRootExpressionUsedInPage() { return ...; }

 

The method “getPageName()” returns the layout definition that is used to render the page bean. The method “getRootExpressionUsedInPage()” returns the expression that is used in this layout definition to identify the page bean instance.

For modifying the layout there are two options:

Defining own layout definition

This is technically the simple way... - you create an own layout definition, which you might copy from the parent bean. You might update the contained expressions to your own expression pattern.

But: of course your own layout is now decoupled from the parent's layout. If the author of the parent layout adds or updates components, you will have to repeat this within your own layout.

Updating the parent's layout definition - IPageModifier

There is a specific modification concept by which you can update the layout that is defined within the parent page bean.

The base of the concept is the interface “IPageModifier” which is a part of each page bean:

public interface IPageBean

{

    ...

    public IPageModifier getPageModifier();

    ...

}

 

public interface IPageModifier

{

    ...

    public void updateParsedNode(ParsedNode node);

    ....

}

 

When reading a layout for a page bean then the page bean is checked if it provides a IPageModifier-instance via the method “getPageModifier()”. If an instance is provided then the layout that is parsed for the page bean can be updated.

Please pay attention: the method getPageModifier() must return the same instance per bean-class! It is not possible to pass back different page modifiers for different page bean instances of the same class.

Updating the parent's layout definition - *.mod.xml definition

There is a default implementation of IPageModifier that is automatically part of any page bean that extends from “PageBean”:

The implementation looks for a “<pageBeanClassName>.mod.xml” file, that resides in the same package as the page bean class.

com

  abc

    def

      XYPageBean.java
     XYPageBean.mod.xml

 

The format of this file is:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<modification>

    <control id="...">

        <attribute name="..." value=”...”/>

        <attribute name="..." value="..."/>

    </control>

    <control id="...">

        <attribute name="..." value=”...”/>

        <attribute name="..." value="..."/>

    </control>

</modification>

 

In this file you can defined updates on components that are part of the layout definition that is coming from the parent component. You just need to reference the id of the component in the parent layout and then can define additional attributes – or modify existing attributes.

Performance Optimization

The (ROW)PAGEBEANINCLUDE component provides two attributes that you may use for performance optimization on server side: UPDATEISOLATION and UPDATEONINNEREVENTONLY.

Performance optimization on server side may be an issue in scenarios, in which you see multiple content pages (page beans) in parallel that do not directly depend from one another.

The demo workplace of CaptainCasa is a good example: if starting a demo via the function tree, then typically all events within the demo do not affect the surrounding workplace functions:


The default request-response processing scans the whole screen for updates with each request. So when pressing the “Add item” button in the demo will mean that within the corresponding roundtrip the whole screen will be processed – including the function tree area at the left, which in principal has nothing to do with the demo on the right.

Using the two attributes you can control, that for a certain area (ROWPAGEBEANINCLUDE) an optimized rendering management is processed:

So the one attribute (UPDATEONINNEREVENTONLY) protects the area from being rendered on outside events – the other attribute (UPDATEISOLATION) protects outside areas from being rendered on inner events.

You may set both attributes to “true” in parallel – and then fully encapsulate your area.

But... - pay attention: By defining one of the attributes you step back from the principle that the whole screen always is automatically updated during a request-response processing!

By calling function “ThreadData.getInstance().registerChangeUpdatingAllAreas()” you can on server side override all optimization and isolation. You need to do this call if you know that a certain updated within your isolated area will also affect data that is rendered in other parts of the screen.

Please note: the two attributes are not really required in typical scenarios. They should be applied in “bigger” scenarios only – and should not be used “just because they exist”...

Preconfigured Popups

The layout and processing of a popup is completely up to you – any page can be called as popup.

CaptainCasa Enterprise Client comes with some default popups in order to support the most common popup scenarios:

The opening and closing of the popups is wrapped by a corresponding Java interface.

OK-Popup

The “OK-Popup” allows to output a text message to the user. The user can confirm the message by clicking an OK button:

The Java code to open the popup on server side is:

public void onOpenOKPopup(ActionEvent ae)

{

    OKPopup.createInstance

    ("Please read",

     "This is the content of the popup.

      Please confirm by pressing OK.");

}

 

Yes/No Popup

The Yes/No popup is similar to the OK popup – but allows the user to decide “Yes” or “No”.

The popup is called by passing an implementation of interface “IyesNoListener”:

public void onOpenYESNOPopup(ActionEvent ae)

{

    YESNOPopup.createInstance

    (

     "Please read",

     "This is the content ... ",

     new IYesNoListener()

     {

         public void reactOnNo()

         {

             Statusbar.outputMessage("No”);

         }

         public void reactOnYes()

         {

             Statusbar.outputMessage("Yes”);                 

         }

      });

}

 

Value Selection Popup with COMBOFIELD

There is a special field component that is designed to be linked with value help popups: the COMBOFIELD component.

Overview

The component supports all properties of the normal FIELD component. It is rendered in a similar way to a combo box – so that the user immediately sees that value help is provided for this field:

There are three ways to activate value help within the component:

In all cases a value help request is sent to the action listener of the component. The event that is passed into the server side action listener method is an event of data type “BaseActionEventValueHelp”.

It's now the server side processing that decides how to support the user. The typical option is to open up a popup window and present the user a list of valid values, or to present a popup allowing the user to search for values.

When opening a popup then there are two types of popups that make sense:

Preconfigured Value Help Popups

For presenting values within a list of valid values there is a preconfigured management including preconfigured popups. Please check the demo workplace for examples and coding details.

Type Ahead Management

A special type of usage is the”type ahead” usage. In this case the user input some text into the field. After the field recognizing that no further input was done for a certain while a value help request is sent to the server. The server side logic now provides a list of reasonable values, the list being already filtered against the user's current input.

The type ahead management is based on the FLUSH, FLUSHTIMER and KEEPFOCUS attribute of the COMBOFIELD component.

A flush event is processed within the action listener. The event is of type “BaseActionEventFlush”. The event provides a method by that you can determine if the flush was caused by a timer: “getFlushWasTriggeredByTimer”.

Result: in your implementation you get notified in two ways when the user wants to get some value help:

Please check the demo workplace for coding details.

 

Working with Menus

CaptainCasa Enterprise Client supports two different types of menus:

Both types of menus work with the same type of components: MENU and MENUITEM, but used in a slightly different way.

Example Reference

Please check the “demopopupmenu.jsp” example for details on JSP layout definition and code.

The menubar is quite simple: it may contain MENU components, and the MENU components themselves contain MENUITEM components. MENUTIEM components are the ones which the user selects in order to invoke a certain server side function.

You can nest multiple MENU items below one MENUBAR so that the menu itself has several hierarchical layers.

Each MENU compopnent has a text (obligatory) and an image (optional).

Each MENUITEM in addition has an ACTIONLISTENER attribute – which is the one called when the user invokes the MENUITEM.

The menu bar can either be statically built inside the layout, i.e. you arrange all the subcomponents as part of the layout definition. Or you dynamically add components using the COMPONENTBINDING attribute.

POPUPMENU

Usage of POPUPMENU

The principles of POPUPMENU management are simple as well:

Example:

<t:rowdemobodypane id="g_3" actionListener="#{d.demoPopupMenu.onBodyAction}" objectbinding="demoPopupMenu" popupmenu="POPUPMENU3" >

  <t:row id="g_4" >

    <t:foldablepane id="g_5" actionListener="#{d.demoPopupMenu.onFoldablePaneAction}" popupmenu="POPUPMENU4" text="Foldable Pane" width="100%" >

      <t:row id="g_6" >

        <t:label id="g_7" actionListener="#{d.demoPopupMenu.onLabel1Action}" popupmenu="POPUPMENU1" text="Label 1" width="120" />

        <t:field id="g_8" actionListener="#{d.demoPopupMenu.onField1Action}" popupmenu="POPUPMENU1" width="200" />

      </t:row>

      <t:rowdistance id="g_9" />

      <t:row id="g_10" >

        <t:label id="g_11" actionListener="#{d.demoPopupMenu.onLabel2Action}" popupmenu="POPUPMENU2" text="Label 2" width="120" />

        <t:field id="g_12" actionListener="#{d.demoPopupMenu.onField2Action}" popupmenu="POPUPMENU2" width="200" />

      </t:row>

    </t:foldablepane>

  </t:row>

  <t:rowdistance id="g_13" height="20" />

  <t:row id="g_14" actionListener="#{d.demoPopupMenu.onRowAction}" popupmenu="POPUPMENU3" >

    <t:textpane id="g_15" text="Some content in this row. The popup menu is defined on row level." width="100%" />

  </t:row>

</t:rowdemobodypane>

 

<t:popupmenu id="POPUPMENU1" >

  <t:menuitem id="g_16" command="OPTION1" text="Option 1" />

  <t:menuitem id="g_17" command="OPTION2" text="Option 2" />

  <t:menuitem id="g_18" command="OPTION3" text="Option 3" />

</t:popupmenu>

 

<t:popupmenu id="POPUPMENU2" >

  <t:menuitem id="g_19" command="OPTION4" text="Option 4" />

  <t:menuitem id="g_20" command="OPTION5" text="Option 5" />

  <t:menuitem id="g_21" command="OPTION6" text="Option 6" />

</t:popupmenu>

 

<t:popupmenu id="POPUPMENU3" >

  <t:menuitem id="g_22" command="OPTION7" text="Option 7" />

  <t:menuitem id="g_23" command="OPTION8" text="Option 8" />

  <t:menuitem id="g_24" command="OPTION9" text="Option 9" />

</t:popupmenu>

 

<t:popupmenu id="POPUPMENU4" >

  <t:menuitem id="g_25" command="OPTION10" text="Option 10" />

  <t:menuitem id="g_26" command="OPTION11" text="Option 11" />

  <t:menuitem id="g_27" command="OPTION12" text="Option 12" />

</t:popupmenu>

 

In the example you see 4 popup menus, that are referenced from diverse components. You can reference the popup menus from various levels of components. In the example they are referenced from “small components” like LABEL and FIELD, as wells as from “big components” such as the ROWBODYPANE.

Event Reaction

The event reaction of the user selecting a popup menu item either is done in the action listener of the MENUITEM or it is done in the action listener of the component referencing the POPUPMENU.

Have a look onto the following page definition:

There are two fields defined, each field being assigned to the same popup menu “FIELD”, that provides two menu items.

The user now can open the same popup menu on both fields. When selecting a menu item on one of the fields the following happens:

On server side two actionListeners are triggered that allow to react on the event:

You may implement and assign both actionListeners or you may implement one of the actionListeners.

Event Reaction on MENUITEM Level

The first option is “just normal”:

<t:popupmenu id="FIELD" >

  <t:menuitem id="g_25" text="Clear" actionListener=”#{xxx.onClear}” command=”CLEAR”/>

  <t:menuitem id="g_26" text="Help" actionListener=”#{xxx.onHelp}” command=”HELP”/>

</t:popupmenu>

 

The action listener “#{xxx.onClear}” is called, the event that is passed is of type “BaseActionEventInvoke”.

In this scenario you do not get any information about the component on which the popup menu was opened. Remember: there may be several components referencing the same POPUPMENU instance.

Use this type of event reaction if the reaction of the user selecting the menu item is always the same for all referencing elements and if the reaction does not depend on the component on which the menu item was selected.

Event Reaction on Component Level

The action listener of the component that references the POPUPMENU is called as well. The event type is “BaseActionEventPopupMenuItem”. Part of the information that is available with the event is the COMMAND string of the MENUITEM instance that was selected by the user.

As consequence you can have multiple components using the same POPUPMENU instance but having a different processing of the menu selection.

On server side you typically have code that looks as follows:

...

...

public void onField1Action(ActionEvent event)

{

    if (event instanceof BaseActionEventPopupMenuItem)

    {

        BaseActionEventPopupMenuItem e = (BaseActionEventPopupMenuItem)event;

        String command = e.getCommand();

        if (“CLEAR”.equals(command))

        {

            ...

        }

        else if (“HELP”.equals(command))

        {

            ...

        }

    }

}

Finding the right POPUPMENU...

Inside a page definition you reference POPUPMENU components by using the attribute POPUPMENU within a component (e.g. a field or a pane). You now can define different popup menus for different areas of the page – e.g. you may want to have a general popup menu that is valid for the whole page (e.g. defined on ROWBODYPANE level) and you may want to have a special popup menu for a certain area (e.g. defined on PANE level).

The client will always select the popup menu, that is adequate. Starting with the component in which the right mouse button was clicked, it steps up the component hierarchy until it finds a POPUPMENU attribute definition. The first definition it finds is the one which is used for building the popup menu.

Hotkey Definitions

The POPUPMENU component offers a second very important feature. It allows to bind a menu item to a key. Pressing the key has the same meaning than opening the popup menu and selecting the menu item with the mouse button.

It may first sound strange that hotkey definitions are made as part of the popup menu definition, but makes sense... for two reasons:

The hotkey definition is done by assigning the so called event keycode to the MENUITEM component's attribute HOTKEY. Open the value help within the layout editor in order to get a list of common keycodes. - In front of the keycode you can write the prefixes “ctrl-”, “alt-”, “shift-” in order to combine the keycode with one (or more) of these special keys.

Addendum – Hotkey in Buttons, Icons, etc.

You define HOTKEY definitions as mentioned if you do not have any “invoker component” that you can attach the hot key to.

You can also directly define hotkey definitions in the HOTKEY attribute of BUTTON, ICON, LINK and other components.

Dynamic Menu Definitions

The MENU/POPUPMENU definitions, that you saw in this chapter so far, are all statically defined popup menus: the popup menu is defined as part of the layout definition.

Of course you can build dynamic popup menus by using the just normal DYNAMICCONTENT component at any position within the MENU/POPUPMENU definition.

Dynamic POPUPMENU – attribute POPUPMENULOADROUNDTRIP

There are some cases in which you want to build the POPUPMENU at this point of time when the user presses with the right mouse button into a certain component.

There is the attribute POPUPMENULOADROUNTRIP which you can define within the component that binds to the POPUPMENU definition. When setting the attribute to “true”, then a roundtrip to the server is triggered when the user presses the right mouse button. The actionListener of the component is called, receiving the event type “BaseActionEventPopupMenuLoad”. Together with the usage of a DYNAMICCONTENT you can now build up the popup menu.

Example: the following screen consists of two “big” labels, both of them with right mouse button support:



The layout definition is:

<t:row id="g_2">

    <t:label id="g_3"

        actionListener="#{d.DemoPopupMenuLoadRoundtrip.onLabelTopAction}"

        font="size:16" popupmenu="CENTRALPOPUP"

        popupmenuloadroundtrip="true"

        text="Right Click onto this top label" />

</t:row>

<t:row id="g_4">

    <t:label id="g_5" text="or" />

</t:row>

<t:row id="g_6">

    <t:label id="g_7"

        actionListener="#{d.DemoPopupMenuLoadRoundtrip.onLabelBottomAction}"

        font="size:16" popupmenu="CENTRALPOPUP"

        popupmenuloadroundtrip="true"

        text="Right Click onto this bottom label" />

</t:row>

<t:popupmenu id="CENTRALPOPUP">

    <t:dynamiccontent id="g_8"

        contentbinding="#{d.DemoPopupMenuLoadRoundtrip.dynMenuContent}" />

</t:popupmenu>

 

In the layout there is only one popup menu definition “CENTRALPOPUP”, internally only holding a DYNAMICCONTENT component. The popup menu is referenced from both “big” of the big labels.

Because with both labels the attribute POPUPMENULOADROUNDTRIP is defined as “true” the corresponding actionListener of each label is called, which then builds up the dynamic menu content:

public void onLabelTopAction(ActionEvent event)

{

    if (event instanceof BaseActionEventPopupMenuLoad)

    {

        List<ComponentNode> nodes = new ArrayList<ComponentNode>();

        {

            ComponentNode node = new ComponentNode("t:menuitem");

            node.addAttribute("text","First of top");

            node.addAttribute("command","TOP1");

            nodes.add(node);

        }

        {

            ComponentNode node = new ComponentNode("t:menuitem");

            node.addAttribute("text","Second of top");

            node.addAttribute("command","TOP2");

            nodes.add(node);

        }

        m_dynMenuContent.setContentNodes(nodes);

    }

    else if (event instanceof BaseActionEventPopupMenuItem)

    {

        BaseActionEventPopupMenuItem e = (BaseActionEventPopupMenuItem)event;

        Statusbar.outputSuccess("Command of popup menu item: " + e.getCommand());

    }

}

 

public void onLabelBottomAction(ActionEvent event)

{

    if (event instanceof BaseActionEventPopupMenuLoad)

    {

        List<ComponentNode> nodes = new ArrayList<ComponentNode>();

        {

            ComponentNode node = new ComponentNode("t:menuitem");

            node.addAttribute("text","First of bottom");

            node.addAttribute("command","BOT1");

            nodes.add(node);

        }

        {

            ComponentNode node = new ComponentNode("t:menuitem");

            node.addAttribute("text","Second of bottom");

            node.addAttribute("command","BOT2");

            nodes.add(node);

        }

        {

            ComponentNode node = new ComponentNode("t:menuitem");

            node.addAttribute("text","Third of bottom");

            node.addAttribute("command","BOT3");

            nodes.add(node);

        }

        m_dynMenuContent.setContentNodes(nodes);

    }

    else if (event instanceof BaseActionEventPopupMenuItem)

    {

        BaseActionEventPopupMenuItem e = (BaseActionEventPopupMenuItem)event;

        Statusbar.outputSuccess("Command of popup menu item: " + e.getCommand());

    }

}

 

 

Working with Drag & Drop

Implementing Drag&Drop is easy! ;-)

Drag&Drop is supported with nearly all components. Currently “internal drag & drop is supported” i.e. You can drag & drop data from one component to the other – you currently cannot drag & drop information from outside (e.g. from Microsoft Excel).

Drag & drop covers two aspects:

Example Reference

Please check details on JSP layout and code within the example “demodragdrop.jsp”.

Details

Attribute DRAGSEND

Each component that supports drag&drop supports the attribute DRAGSEND. This attribute contains a string that represents the content of the component.

The string is built in the following way: <dragtype>:<draginfo>, e.g. a string might be “article:4711”.

The DRAGSEND information is carried from one component to the next during a drag & drop operation.

Attribute DROPRECEIVE

Each component that supports drag & drop supports the attribute DROPRECEIVE. This attribute contains a semicolon separated list of all drag-types that can be dropped onto the component.

E.g. a component defines DROPRECEIVE to be “article;customer;file”.

By defining the DROPRECEIVE attribute a component specifies which types of drag data can be dropped onto the component.

Event Processing in the Server Side Java Code

At the point of time when the user drops information onto a component, the component that receives the drop event calls its ACTIONLISTENER.

In the action listener there are two event types that represent the drag & drop operation:

The “BaseActionEventDropCopy” inherits from “BaseActionEventDrop”. The main method both events provides is the getDragInfo() method. This returns the information behind the DRAGSEND attribute of the component where the drag & drop was started from.

Information associated with Drop Event

Please take a look into the Java documentation of the drop event (class BaseActionEventDrop). There is quite important information associated with the event:

Controlling the Component Highlighting during Drag & Drop

By default, when the user drags a certain information from one component to others, the user interface behaved in the following way:

You can choose other types of highlighting as well, by building up the DROPRECEIVE information in the following way: “<dragtype>:<dropZoneInfo>”. The following “drop zone info” definitions are available:

Example: instead of defining DROPRECEIVE as “article” you can define DROPRECEIVE as “article:horizontalsplit”.

As result of explicitly assigning a drop zone information, the highlighting when moving the mouse over the component will be updated according to the definition, e.g. in case of using “horizontalsplit” only the left or the right area of the component will be highlighted during a drag & drop operation.

Please note: the definition of a “<dropZoneInfo>” is a pure optical advice, that the client uses for highlighting purposes. In case of dropping information you still need to figure out the drop zone on your own, by accessing the drop position that is associated with the drop event.

The following graphics illustrates the “sensitive” areas when using diverse dropzones:


The lines in the graphics are at the position 0, 25%, 50%, 75%.

Working with Shapes

CaptainCasa Enterprise Client provides a simple but efficient way to create shape graphics:

The features that are provided are:

Components

There are only a few components which you need to know about:

Example

Have a look onto the following page:

The corresponding layout definition is rather simple:

<t:rowdemobodypane id="g_1" objectbinding="#{d.DemoPaintAreaSimple}">

  <t:row id="g_2">

    <t:paintarea id="g_3" background="#FFFFFF" height="100%" width="100%">

 

      <t:paintareaitem id="g_4" bgpaint="roundedrectangle(0,0,100%,100%,20,20,#C0C0FF,#F0F0FF,vertical);write(50%,50%,Person A,centermiddle)" bounds="50;50;90;60" />

 

      <t:paintareaitem id="g_5" bgpaint="roundedrectangle(0,0,100%,100%,20,20,#C0FFC0,#F0FFF0,vertical);write(50%,50%,Person B,centermiddle)" bounds="250;150;90;60" />

 

      <t:paintarealineitem id="g_6" arrowfrom="1" arrowto="2" bounds="140;80;110;100" />

 

      <t:paintareapaneitem id="g_7" bgpaint="#C0C0C0" border="#808080"

                         bounds="50;250;290;52" padding="5" rowdistance="2">

        <t:row id="g_8">

          <t:label id="g_9" text="First Name" width="100" />

          <t:field id="g_10" width="100%" />

        </t:row>

        <t:row id="g_11">

          <t:label id="g_12" text="Last Name" width="100" />

       <t:field id="g_13" width="100%" />

        </t:row>

      </t:paintareapaneitem>

 

      <t:paintarealineitem id="g_14" arrowfrom="4" arrowto="6" bounds="95;110;0;140" />

 

    </t:paintarea>

  </t:row>

</t:rowdemobodypane>

 

You see:

Getting into more complex Scenarios

The step from “simple” to “complex” is not too far:

Check the examples within the demo workplace for more information.

Working with Managed Beans

This chapter tells you about referencing properties of managed beans from user interface components. And it tells you about useful ways to add certain application functions into the server side request processing.

Basics

Attributes of a user interface component (e.g. the TEXT attribute of a LABEL component) can be either defined in a static way (“First Name”) or in a dynamic way (“#{xxx.yyy}”). - Well, there are two exceptions of this rule – the attribute STYLEVARIANT and ATTRIBUTEMACRO can only be set in a static way, but this is the only exception.

A managed bean is a Java Bean object (at runtime) that is created and managed by the Java Server Faces environment.

A managed bean is declared in the faces-config.xml configuration file, telling the JSF environment about...:

A valid faces-config.xml definition may look like:

<?xml version="1.0" encoding="UTF-8"?>

 

<faces-config

  xmlns="http://java.sun.com/xml/ns/javaee"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"

    version="1.2">

 

<managed-bean>

  <managed-bean-name>demoHelloWorld

  </managed-bean-name>

  <managed-bean-class>test.DemoHelloWorld

  </managed-bean-class>

  <managed-bean-scope>session

  </managed-bean-scope>

</managed-bean>

    

</faces-config>

 

A managed bean provides:

that can be referenced from page definitions.

Dynamic Properties

Usage of Maps

The expression language that is used for referencing managed bean properties allows to gently work with Maps (java.util.Map-instances).

Assume the bean “aaaaa” of the previous example is implemented in the following way:

public class AaaaaBean ...

{

    HashMap m_values = new HashMap();

    public Map getValues()

    {

        return m_values;

    }

 

    ...somewhere...

    {

        m_values.put(“firstName”,”....”);

        m_values.put(“lastName”,”....”);

    }

}

 

In this case you can reference the map's items in the following straight way:

You see: properties can be either hard-coded by providing a corresponding set/get implementation or can be soft-coded by using maps.

Usage of Collections / Arrays

In the same way as described with maps you may define lists (implementations of java.util.List) or object arrays and reference them with expressions:

public class AaaaaBean ...

{

    List m_persons = new ArrayList<Person>();

    public List getPersons()

    {

        return m_persons;

    }

 

}

 

The expression to reference a certain item of the list look like:

Dynamic Properties – Dynamic Bean Browser

When using dynamic properties then you may also want to reflect dynamic properties within the Bean Browser – the tool on the right of the Layout Editor, from which you can drag & drop expressions from the bean hierarchy into the component attributes.

There is a certain mechanism that allows to dynamically provide the property information for the bean browser. Please check the corresponding appendix of this documentation.

Data Type Considerations

...without doing anything special...

With “fix-coded” properties, that provide a set/get method, things are simple: the data type that is used within the set/get-method definition is the one which is transferred into the object once the user updates the value on client side.

With dynamic attributes things are a bit more complex – but still simple:

In short words: for dynamic content (map, list, ...) first the server side processing tries to stick to the data type of the value which is already available. If none is available then a data type is chosen that fits to the component - if there is no specify one, “String” is chosen.

...with explicitly passing back type information...

In addition to the default mechanism described in the previous section you can explicitly implement an interface in order to pass back data type information:

package org.eclnt.jsfserver.util;

 

public interface IPropertyTypeResolver

{

    public Class resolveType(String propertyName);

}

 

You may implement this interface on any level that is accessed by expressions.

Property Binding within Grid Processing

Within the grid processing (i.e. list and tree processing using FIXGRID component), there are the following binding rules:

Accelerated Property Access

When not using dynamic property binding, i.e. when not using maps or arrays, then the resolution of properties is done via normal Java introspection. For each property that is referred to within an expression there is a corresponding getter-/setter-method that is accessed correspondingly.

Example: if the expression is “#{d.TestUI.firstName}”, then the bean behind “TestUI” has a corresponding pair of methods:

public class TestUI

{

    public void setFirstName(String value) { ... }

    public String getFirstName() { ... }

}

 

The introspection of properties and their setter-/getter-methods is powerful on the one hand, but from performance of view is much slower than a direct access to the set/get method.

That's the reason why a special interface can accelerate the resolution of data:

package org.eclnt.jsfserver.util;

 

public interface IAcceleratedPropertyAccess

{

    public final static Object NOT_AVAILABLE = new Object();

    public Object getPropertyValue(String property);

}

 

If a managed bean implements this interface then the property resolution will first try to load the property via the interface. If the interface passes back a value that is not the NOT_AVAILABLE object, then this value will be used – otherwise the normal introspection will be done.

The class “TestUI” could as consequence be accelerated in the following way:

public class TestUI implements IAcceleratedPropertyAccess

{

    public Object getPropertyValue(String property)

    {

        if (“firstName”.equals(property))

            return getFirstName();

        return NOT_AVAILABLE;

    }

 

    public void setFirstName(String value) { ... }

    public String getFirstName() { ... }

}

 

The interface is only available for the getting of values – because this is executed much more often than the setting of values.

You typically should think about using the interface every time you have one and the same type of object being accessed a lot of times (e.g. accessed in grids).

Method Binding

Basics

The same rules that are used for referencing properties are used for referencing methods. A method expression (“#{d.aaaa.onXyz}”) must have a counter part method within the referenced bean. The method needs to have the following signature:

public class AaaaaBean ...

{

    ...

    ...

 

    public void onXyz(ActionEvent event)

    {

        ...

        ...

    }

 

    ...

    ...

}

 

One method, various events

All components of CaptainCasa Enterprise Client pass an event of type “BaseActionEvent”, which is a subclass of ActionEvent:

    public void onXyz(ActionEvent event)

    {

        BaseActionEvent bae = (BaseActionEvent)event;

        ...

    }

 

The base action event refers to the actual client event that triggered the request processing. It provides the functions:

Please note: one component can be associated with different commands. Example: a FIELD component has one actionListener-method in which the following events can be passed:

All events for these three commands are passed to one and the same method, that is the action listener method for this component. In order to better separate events from one another there is a sub-class of BaseActionEvent for each command:

BaseActionEvent

    ...

    BaseActionEventDrop

    BaseActionEventDropCopy

    BaseActionEventFlush

    ...

 

Then name of the sub-class always is “BaseActionEvent+Command”. Each subclass provides – if required – straight access methods to the parameters that are associated with an event.

An event may be associated with additional data that is sent as event parameters. You can either access the parameters by the getParams() method of BaseActionEvent or you may use concrete accessor methods of the corresponding subclass of BaseActionEvent.

The typical structure of the method that acts as action listener looks like:

    public void onXyz(ActionEvent event)

    {

        if (ae instanceof ActionEventFlush)

        {

            ...

        }

        else if (ae Instanceof ActionEventDrop)

        {

            ...

        }

        else if (ae Instanceof ActionEventDropCopy)

        {

            ...

        }

    }

 

Tool support

When maintaining component attributes within the Layout Editor then you will see a list of all commands that are available with a component:

The list of commands will look different, depending from the value of attributes.

Adapter Binding

Since CaptainCasa Enterprise Client Release 3.0 there is a special type of binding available, the so called “adapter binding”.

Purpose

A component, e.g. a field, provides a quite high number of attributes to define the way it looks and the way it behaves. In many cases the attribute values are defined by expressions, so that at runtime the attribute value is taken from server side bean property values.

Example: in case of a field the definition may look like:

<t:field ...

         text=”#{d.xyz.name}”

         enabled=”#{d.xyz.nameEnabled}”

         rendered=”#{d.xyz.nameRendered}”

         bgpaint=”#{d.xyz.nameBgpaint}”

         .../>

 

There is a quite high effort for defining the server side structures and for defining the expressions for each attribute. To reduce this effort CaptainCasa provides two possibilities that you can select from:

Basic Idea

The basic idea is very simple: a component (e.g. the field) is bound to a so called adapter object. The adapter object is a “mini object” directly providing certain attributes for the component – so that the component delegates value requests directly to the adapter object. The adapter object is some kind of “counter part” for the component.

The components that support the usage of adapter objects provide an attribute ADAPTERBINDING, that needs to point to an stable instance of adapter object.

Definition of Adapter Objects

Adapter objects implement the following interface:

public interface IComponentAdapterBinding

{

    public Set<String> getFixAttributeNames();

    public Set<String> getDynamicAttributeNames();

    public Class getAttibuteType(String attributeName);

    

    public void setAttributeValue(String attributeName, Object value);

    public Object getAttributeValue(String attributeName);

    

    public void onAction(ActionEvent event);

}

 

The adapter tells the component which attributes it takes responsibility for, and then provides the corresponding set/get-access methods.

In case the component supports an action listener, the adapter's “onAction” method is called.

What happens at Runtime

At runtime the component (e.g. within the render phase of the request processing) gathers all its attribute values in order to check if updated information needs to be sent to the client side. The corresponding attribute values are requested from the adapter object – if the component is created new then all (both the fix and dynamic attributes) are requested, if the component is already existing then only the dynamic attributes are requested.

If the component is able to updated a certain attribute (e.g. a FIELD component updates its TEXT attribute), then the update is done through the corresponding “setAttributeValue” method – in the case of update objects you also need to implement the “getAttributeType” method for the update-attribute.

Example

The following example shows how to use the adapter binding for simple scenarios – in order to then cover more and more complex scenarios.

There are two fields, depending on the input a certain background painting is shown.

The jsp layout definition is:

<t:row id="g_2">

    <t:label id="g_3" text="First Name" width="100" />

    <t:field id="g_4"

        adapterbinding="#{d.DemoAdapterBindingSimple.firstName}"

        width="100" />

</t:row>

<t:row id="g_5">

    <t:label id="g_6" text="Last Name" width="100" />

    <t:field id="g_7"

        adapterbinding="#{d.DemoAdapterBindingSimple.lastName}"

        width="100" />

</t:row>

<t:row id="g_8">

    <t:coldistance id="g_9" width="100" />

    <t:button id="g_10"

        actionListener="#{d.DemoAdapterBindingSimple.onCheck}"

        text="Check" />

</t:row>

 

You see that both field components are bound via adapter binding.

The code is:

public class DemoAdapterBindingSimple

    implements Serializable

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

 

    static Set<String> MYFIELDADAPTER_FIXATTRIBUTES;

    static Set<String> MYFIELDADAPTER_DYNATTRIBUTES;

    

    static

    {

        MYFIELDADAPTER_FIXATTRIBUTES = new HashSet<String>();

        MYFIELDADAPTER_DYNATTRIBUTES = new HashSet<String>();

        MYFIELDADAPTER_DYNATTRIBUTES.add("text");

        MYFIELDADAPTER_DYNATTRIBUTES.add("enabled");

        MYFIELDADAPTER_DYNATTRIBUTES.add("bgpaint");

    }

    

    public class MyFieldAdapter implements IComponentAdapterBinding

    {

        boolean i_containsError = false;

        String i_text = "";

        boolean i_enabled = true;

        public Set<String> getFixAttributeNames()

        { return MYFIELDADAPTER_FIXATTRIBUTES; }

        public Set<String> getDynamicAttributeNames()

        { return MYFIELDADAPTER_DYNATTRIBUTES; }

        public Class getAttibuteType(String attributeName)

        {

            if ("text".equals(attributeName))

                return String.class;

            else

                throw new Error("The attribute " + attributeName +

                                " is not supported!");

        }

        public Object getAttributeValue(String attributeName)

        {

            if ("text".equals(attributeName))

                return i_text;

            else if ("enabled".equals(attributeName))

                return i_enabled;

            else if ("bgpaint".equals(attributeName))

            {

                if (i_containsError == true)

                    return "error()";

                else

                    return null;

            }

            else

                throw new Error("The attribute " + attributeName +

                                " is not supported!");

        }

        public void setAttributeValue(String attributeName, Object value)

        {

            if ("text".equals(attributeName))

                i_text = (String)value;

            else

                throw new Error("The attribute " + attributeName +

                                " is not supported!");

        }

        public void onAction(ActionEvent event) {}

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    MyFieldAdapter m_firstName = new MyFieldAdapter();

    MyFieldAdapter m_lastName = new MyFieldAdapter();

    

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

 

    public MyFieldAdapter getFirstName() { return m_firstName; }

    public MyFieldAdapter getLastName() { return m_lastName; }

    

    public void onCheck(ActionEvent event)

    {

        m_firstName.i_containsError = false;

        m_lastName.i_containsError = false;

        // some "checks"

        if (m_firstName.i_text.startsWith("_"))

            m_firstName.i_containsError = true;

        if (m_lastName.i_text.endsWith("!"))

            m_lastName.i_containsError = true;

    }

    

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

 

}

 

The class “MyFieldAdapter” is defined as inner class. It takes over responsibility for the attributes “text”, “bgpaint”, “enabled”. Please not that all properties are defined within the set of dynamic properteis. And, please note: even though there is no fix attribute that is provided by “MyFieldAdapter” you need to return an empty set.

The value of “bgpaint” is derived from a flag indicating if the adapter binding contains an error or not (“i_containsError”).

Overriding Adapter Binding

If an attribute that is provided by an adapter binding is explicitly defined within the .jsp/.xml layout definition, then (of course...) the explicitly defined value overrides the value coming from the adapter binding.

Related Information

There are some helper classes implementing IComponentAdapterBinding: “ComponentAdapterBindingBase” and “ComponentAdapterBindingMap”. Please check the JavaDoc if these helper classes are useful for you.

Please check the chapter “Working with Macros” as well, which provides a – in some parts similar way – of simplifying the working with “rich sets of dynamic attributes per component”.

Adapter Binding with Annotations

Based on the adapter binding that is described in the previous chapter there are nice implementations of the IComponentAdapterBinding interface which take use of annotations so that the implementation of adapter classes is simplified significantly.

Base class ComponentAdapterByAnnotation

Take a look onto the following implementation:

public class FieldAdapter extends ComponentAdapterByAnnotation

{

    String m_label;

    String m_value;

    boolean i_containsError;

    String i_errorText;

    public FieldAdapter(String label)

    {

        m_label = label;

    }

    @ComponentAttribute(type=ComponentAttributeType.FIX,attribute="labeltext")

    public String getLabel() { return m_label; }

    @ComponentAttribute(attribute="text")

    public String getValue() { return m_value; }

    public void setValue(String value) { m_value = value; }

    @ComponentAttribute

    public String getBgpaint()

    {

        if (i_containsError)

            return "error()";

        else

            return null;

    }

    @ComponentAttribute

    public String getTooltip()

    {

        if (i_containsError == true)

            return i_errorText;

        else

            return null;

    }

    public void indicateError(String text)

    {

        i_containsError = true;

        i_errorText = text;

    }

    public void resetError()

    {

        i_containsError = false;

        i_errorText = null;

    }

}

 

By using the annotation “@ComponentAttribute” you directly can bind property implementations to component attributes. There are two parameters that you may pass with an annotation:

All the methods that are part of interface “IComponentAdapterBinding” now are automatically resolved from the annotation definitions.

Please take a look into the demo workplace, section “Adapter Binding” to see an example of using this class in the context of a complete page bean.

Base class ComponentAdapterByAnnotationForBeanProperty

In many cases the “core data” that is shown/updated by a component is a property of an existing bean (e.g. Pojo-object).

Example: from the database you read an entity “Person” and receive a “Person” instance:

public class Person implements Cloneable

{

    public final static String PROP_FIRSTNAME = "firstName";

    public final static String PROP_LASTNAME = "lastName";

    public final static String PROP_TITLE = "title";

    public final static String PROP_DEPARTMENT = "department";

    public final static String PROP_COMMENT = "comment";

    public final static String PROP_GENDER = "gender";

    

    String m_id = UniqueIdCreator.createUUID();

    int m_gender = 0; // 0=male, 1=female, 2=diverse

    String m_firstName;

    String m_lastName;

    String m_title;

    String m_department;

    String m_comment;

    

    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; }

 

    public String getComment() { return m_comment; }

    public void setComment(String comment) { m_comment = comment; }

}

 

In a FIELD-component you then want to bind the field edit content directly to some property of the Person-instance.

This is exactly the purpose of the class “ComponentAdapterByAnnotationForBeanProperty”. It takes over the concept of binding component attributes via annotation – but adds in addition the ability that the core content is bound to a bean-property.

Example:

public class PropertyAdapter
   extends ComponentAdapterByAnnotationForBeanProperty<Person>

{

    String i_label;

    boolean i_containsError;

    String i_errorText;

    boolean i_mandatory = false;

    public PropertyAdapter(String componentAttribute,

                           IBeanAccess<Person> beanAccess,

                           String property,

                           String label)

    {

        super(componentAttribute, beanAccess, property);

        i_label = label;

    }

    @ComponentAttribute(type=ComponentAttributeType.FIX,attribute="labeltext")

    public String getLabel() { return i_label; }

    @ComponentAttribute

    public String getBgpaint()

    {

        if (i_containsError)

            return "error()";

        else if (i_mandatory)

            return "mandatory()";

        else

            return null;

    }

    @ComponentAttribute

    public String getTooltip()

    {

        if (i_containsError == true)

            return i_errorText;

        else

            return null;

    }

    public void indicateError(String text)

    {

        i_containsError = true;

        i_errorText = text;

    }

    public void resetError()

    {

        i_containsError = false;

        i_errorText = null;

    }

    public void setMandatory(boolean value) { i_mandatory = value; }

}

 

In the constructor of “ComponentAdapterByAnnotationForBeanProperty” you pass:

All the rest is the same as with “ComponentAdapterByAnnotation”.

Please take a look into the demo workplace, section “Adapter Binding” to see an example of using this class in the context of a complete page bean.

Registration of component that uses adapter binding

Since update 20210310 there is an extended version “IComponentAdapterBinding2”. This version provides a method...

public void initComponent(IBaseComponent component);

 

...that you can implement. The method passes the component into the adapter binding that actually is referring via expression to the adapter binding. As result you can influence the inner functions of your implementation to specific requirements of the using component.

Please pay attention: the component is passed during the render-phase of the request processing!

Using “lazy loading” in Map-implementations

In dynamic scenarios it is useful to store component adapter instances within a page bean inside a Map-instance. Example:

public class XyzUI extends PagebBean

{

    public class MyAdapter extends ComponentAdapterByAnnotation

    {

        ...

    }

 

    Map<String,MyAdapter> m_adapters = new HashMap<String,MyAdapter>();

 

    public XyzUI()

    {

        m_adapters.put(“firstName”, new MyAdapter(...));

        m_adapters.put(“lastName”, new MyAdapter(...));

    }

 

    ...

    public Map<String,MyAdapter> getAdapters() { return m_adapters; }
   

}

 

In this case the layout definition's components refer to this map in the following way:

...

    <t:field adapterbinding=”#{d.XyzUI.adapters.firstName}” .../>

...

    <t:field adapterbinding=”#{d.XyzUI.adapters.lastName}” .../>

...

 

The page bean class has to provide for the proper instances of the adapter and has to register this instance in the adapters-map.

This scenario can be simplified by using Map-implementations with a lazy loading concept. This means: the get()-method of the Map-implementation checks if the object already is available – and if not requests the creation of the object.

CaptainCasa provides the Map-implementation “LazyLoadingMap” which exactly does this:

public class LazyLoadingMap<VALUECLASS extends Object> extends

    HashMap<String,VALUECLASS>

{

    public interface ILazyLoader<BEANCLASS>

    {

        public BEANCLASS lazyLoad(String key);

    }

 

    public LazyLoadingMap() { ... }

    public LazyLoadingMap(ILazyLoader<VALUECLASS> loader) { ... }

 

    public void setLoader(ILazyLoader<VALUECLASS> loader) {...}

    ...

}

 

In case the object is not yet loaded, the map calls the interface ILazyLoader in order to request a correspond instance.

Exception Management

The normal exception management within the JSF processing is not specified too much – but leaves the exception processing to the servlet container. In order to provide some more “nicer” exception management, the CaptainCasa sever side framework provides some additional functions on top of JSF.

During a server side request life cycle there are three important phases of an application processing:

(Yes, there are some more phases defined in JSF but these are left out here for simplification purpose.)

From consistency point of view the data transfer phase and the invoke phase are most critical:

The render phase is not as critical because no data is changed within this phase – but data is just being picked.

Default Handling of Exception

By default an exception that is thrown by your application processing during the data transfer and the invoke phase leads to an abort of the current processing. The user received an error screen in which the exception information is shown. This error screen can be customized – you find more information about how to define the error screen in one of the following chapters.

By default an exception that is thrown by your application processing during the render phase is ignored – there are some information messages that are registered within the log, that's it.

Additional Functions on top of JSF

During the data transfer (“set”) and the invoke (“actionListener”) phase there are the following additional functions:

Uuuh, this sounds complex, but indeed isn't. Example:

A button is bound to “#{d.aaa.bbb.onXyz}”. Let's check what happens...:

#{d.aaa.bbb.onXyz} ==> onXyz is called!

                   !!! onXyz processing throws exception/error

 

#{d.aaa.bbb.onApplicationError} is called, if available

#{d.aaa.onApplicationError} is called, if available

ä{d.onApplicationError] is called, if available

 

Please note: the “Walking up the expression stack” is interrupted after the first “onApplicationError” method was processed without throwing an exception/error itself.

The same happens when during the data transfer phase a property is set:

#{d.aaa.bbb.firstName} ==> setFirstName(...) is called

                       !!!setFirstName processing throws exception/error

 

#{d.aaa.bbb.onApplicationErrorDuringSet} is called, if available

#{d.aaa.onApplicationErrorDuringSet} is called, if available

ä{d.onApplicationErrorDuringSet] is called, if available

 

Consequences for Implementation

A normal impementation, without using the additional error management may look like:

    public void onXyz(ActionEvent event)

    {

        try

        {

            ...

            ...

        }

        catch (RuntimeException r)

        {

            Statusbar.outputAlert(...);

        }

        catch (Error e)

        {

            Statusbar.outputAlert(...);

        }

    }

 

With using the optional exception management your code now looks the following way:

    public void onXyz(ActionEvent event)

    {

        ...

        ...

    }

 

    public void onApplicationError(ApplicationErrorInfo aei)

    {

        Statusbar.outputAlert(...);

    }

 

This means: in case a method (“onXyz”) causes an error, automatically a method “onApplicationError()” is called. This method then can then handle the error management, i.e. It may do some output to the status bar.

All information about the original error is passed via the “ApplicationErrorInfo” object.

You may use the interface “IErrorAware” in order to implement the “onApplicationError*” methods on the corresponding object level. (The calling of the methods does not depend on the extension of this interface, so the interface is “just” a helper for implementation purposes.)

package org.eclnt.jsfserver.util;

 

import org.eclnt.jsfserver.elements.ApplicationErrorInfo;

import org.eclnt.jsfserver.elements.ApplicationErrorInfoDuringSet;

 

public interface IErrorAware

{

    /**

     * This method is called in case of an error when a method expression

     * is executed during the INVOKE-phase of the JSF processing.

     * <br><br>

     * In case this method throws an error/ runtime exception itself

     * then this error is delegated correspondingly.

     */

    public void onApplicationError(ApplicationErrorInfo aei);

    

    /**

     * This method is called in case of an error when a set-value-expression

     * is executed during the SET-phase of the JSF processing.

     * <br><br>

     * In case this method throws an error/ runtime exception itself

     * then this error is delegated correspondingly.

     */

    public void onApplicationErrorDuringSet(ApplicationErrorInfoDuringSet aeids);

}

 

Customizing the Server Side Error Screen

The default screen that comes up if an error was not caught by the application processing is:


The name of the corresponding page is “/eclntjsfserver/includes/showservererror.jsp”.

You may define your own error screen layout (e.g. copy the original page and modify it). The name of the updated page needs to be passed as client parameter “errorscreen”.

Best Practice: Checking if your whole Application is available

There are certain checks that you may want to do in general, with every request. E.g. check if your application is connected to the database, if a user is logged on, etc. In this case you may define your “out-most” page in the following way:

<t:rowinclude page=”/rescuepage.jsp” rendered=”#{d.appNotOK}”/>

<t:rowinclude page=”/normalpagef” rendered=”#{d.appOK}”/>

 

“appOK/appNotOK” are booleans that check the health-status of the application. If the application is not healthy anymore then the rescuepage is shown, otherwise the normal application is shown.

Property Change Notification

There are a couple of interfaces that are considered when a property value is set into a managed bean. The interfaces are executed (if available) during the JSF phase, in that all client side value changes are transferred into the corresponding managed beans (“update phase”).

Who is calling the interfaces? It's the CaptainCasa/JSF environment. JSF allows to bring in own processing in various parts of the normal request processing. E.g. for transferring a value that was changed within the user interface client, there is a so called “property resolver”. CaptainCasa provides own resolvers that provide the additional functions to check for the interfaces that are described within the following text.

Please also check the JavaDoc documentation that is available for the interfaces.

Interface IPropertyResolverAware

A bean that is updated by the user interface request processing may optionally implement the interface “IPropertyResolverAware”:

package org.eclnt.jsfserver.util;

 

public interface IPropertyResolverAware

{

    public void reactOnSetValue(Object property, Object value);

    public void reactOnSetValue(int index, Object value);

}

 

The interface is used by the so called property resolver: the property resolver is an object within the CaptainCasa JSF processing that is responsible for passing values coming from the user interface client into corresponding beans that are bound by expressions (e.g. “#{d.address.street”). The interface is called after the corresponding value was set.

In case the property resolver updates the property of a bean, it will check if the bean implements the IPropertyResolverAware-interface. If yes, then the corresponding method of the interface is called.

Example: if the value “#{d.address.street}” is set, then the bean behin “#{d.address}” will be checked for the implementation of IPropertyResolverAware.

Interface IPropertyResolverAware2

This interface is similar to IPropertyResolverAware, but not only tells about direct value changes to the current object, but also tells about value changes within a sub-object of the current object.

public interface IPropertyResolverAware2

{

    public void reactOnSetValue(String completeExpression,

                                String propertyName,

                                Object value);

    

    public void reactOnSetValue(String completeExpression,

                                int index,

                                Object value);

    

    public void reactOnSetValueInSubObject(String completeExpression,

                                           Object value);

}

 

Interface IPropertyValueConverter

In special situations it is required to change objects coming from the user interface before transferring them into the corresponding object-property. This is when you may use the interface IPropertyValueConverter:

public interface IPropertyValueConverter

{

    public Object convertObject(Object property, Object value);

    public Object convertObject(int index, Object value);

}

 

The interface is called before the value is set into the corresponding property. You may update the value within your interface implementation.

Interface IValueBindingListener

While the interfaces IPropertyResolverAware and IPropertyValueConverter operate on the last bean level, the one where the property is set, the interface IValueBindingListener is a more general one. It gets notified whenever a value binding is executed within the JSF phase when values are transferred into the managed beans.

package org.eclnt.jsfserver.util;

 

public interface IValueBindingListener

{

    public void reactoOnValueBindingSet(ValueBinding valueBinding,

                                        Object value);

}

 

For using the interface, you need to...:

An example is:

    // implementation if IValueBindingListener

    public class MyValueBindingListener implements IValueBindingListener

    {

        String m_log = “”;

        public void reactoOnValueBindingSet(ValueBinding valueBinding,

                                            Object value)

        {

            m_log += "IValueBindingListener, Property change: " +

                     ValueBindingUtil.getOriginalExpressionString

                                      (valueBinding) +

                     " / " + value + "\n";

        }

    }

 

 

    // somewhere in the initialization part of your code:

    HttpSessionAccess.setValueBindingListener

                      (new MyValueBindingListener());

 

Class CascadingValueBindingListener

There is an implementation of IValueBindingListener that comes with the CaptainCasa standard delivery: CascadingValueBindingListener. This implementation notifies each object level of an expression that is just set, so that the corresponding level can react.

Example: if a value is passed into “#{d.d_1.PersonUI.firstName}”, then all object levels of the expression are notified:

#{d.d_1.PersonUI}

#{d.d_1}

#{d}

 

As consequence you can e.g. set flags, in which you keep the information that something has changed within the corresponding object. The notification is done by checking if each object supports interface ICascadingValueBindingListener – the interface is an inner class definition within CascadingValueBindingListener.

In the following example on grid item level this change information is managed by using CascadingValueBindingListener:

package workplace;

 

import java.io.Serializable;

 

import javax.faces.el.ValueBinding;

import javax.faces.event.ActionEvent;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.elements.impl.FIXGRIDItem;

import org.eclnt.jsfserver.elements.impl.FIXGRIDListBinding;

import org.eclnt.jsfserver.util.CascadingValueBindingListener;

import org.eclnt.jsfserver.util.HttpSessionAccess;

import org.eclnt.jsfserver.util.CascadingValueBindingListener.ICascadingValueBindingListener;

import org.eclnt.workplace.IWorkpageDispatcher;

 

@CCGenClass (expressionBase="#{d.DemoCascadingValueBinding}")

 

public class DemoCascadingValueBinding

    extends DemoBase

    implements Serializable

{

    // ---------------------------------------------------------------

    // inner classes

    // ---------------------------------------------------------------

    

    public class GridItem

        extends FIXGRIDItem

        implements java.io.Serializable, ICascadingValueBindingListener

    {

        boolean m_changed = false;

        

        protected String m_carId;

        public String getCarId() { return m_carId; }

        public void setCarId(String value) { m_carId = value; }

        

        protected String m_carType;

        public String getCarType() { return m_carType; }

        public void setCarType(String value) { m_carType = value; }

        

        public boolean getChanged() { return m_changed; }

        

        public void reactOnSetValue(ValueBinding arg0,

                                    ValueBinding arg1,

                                    Object arg2)

        {

            m_changed = true;

        }

    }

    

    // ---------------------------------------------------------------

    // members

    // ---------------------------------------------------------------

    

    protected FIXGRIDListBinding<GridItem> m_grid = new FIXGRIDListBinding<GridItem>();

    protected String m_street;

    protected String m_lastName;

    protected String m_firstName;

    

    // ---------------------------------------------------------------

    // constructors

    // ---------------------------------------------------------------

    

    public DemoCascadingValueBinding(IWorkpageDispatcher dispatcher)

    {

        super(dispatcher);

        HttpSessionAccess.setValueBindingListener

                          (new CascadingValueBindingListener());

        for (int i=0; i<10; i++)

        {

            GridItem gi = new GridItem();

            gi.setCarType("Volkswagen");

            gi.setCarId("HD - BM 666"+i);

            m_grid.getItems().add(gi);

        }

    }

    

    // ---------------------------------------------------------------

    // public usage

    // ---------------------------------------------------------------

    

    // ---------------------------------------------------------------

    // private usage

    // ---------------------------------------------------------------

 

    public FIXGRIDListBinding<GridItem> getGrid() { return m_grid; }

 

}

 

JSF Phase Management

Overview

During a server side request processing the following activities are executed:

JSF knows some additional phases which are currently not relevant when using CaptainCasa Enterprise Client.

Starting Runnables to be executed in a certain Phase

In some cases it makes sense that managed beans are aware of certain JSF phases, that are processed during a request processing. The most important JSF phases that are relevant for CaptainCasa Enterprise Client processing are:

Example: values are passed into a status bar in order to be output on client side. But: the values should be automatically set back to initial values, in order to not show the same message again and again.

The base for listening to JSF phases is provided by JSF itself: there is the possibility to register phase listeners within the faces-config.xml file. These phase listeners are called during request processing and can communicate to the application by using the JSF context (FacesContext). Please read more details about this within the JSF documentation.

CaptainCasa Enterprise Client provides a simple mechanism which is based on the JSF phase listener concept that allows to simply attach “Runnables” to the beginning or the ending of certain phases. The interface is pretty simple:

public class PhaseManager

{

    ...

    public static void runAfterRenderResponsePhase(Runnable run) {...}

    public static void runBeforeRenderResponsePhase(Runnable run) {...}

    ...

}

 

The runnable object is executed at the point of time represented by the method name. Currently the PhaseManager supports “Runnables” that are executed before or after the “render response phase”.

Let's assume you want to output an error information only one time to the client. The coding might look the following way:

public class XYZBean implements Serializable

{

    public static class OutputTextClearer implements Runnable, Serializable

    {

        public void run()

        {

            m_outputText = “”;

        }

    }

    ...

    ...

    public void onSave(ActionEvent event)

    {

        ...

        m_outputText = “Error occurred when saving”;

        PhaseManager.runAfterRenderResponsePhase(new OutputTextClearer());

        ...

    }

    ...

    ...

}

 

The method “onSave” is called in the invoke phase. After the invoke phase the render phase is processed, in which the XML is built that is sent to the client. At the end of the phase the runnable is called, setting back the outputText-value.

After executing the runnables of one phase, the runnables are removed. Runnables are processed only one time.

Adding some BEANPROCESSING Statements into the Page

In addition to adding “Runnables” by program (previous chapter) you also can add “Runnables” by declaration. There is a component BEANPROCESSING and a component BEANMETHODINVOKER to do so.

Have a look onto the following example:

The user can input numbers into the grid. Every time a request is processed on server side, the profit and the aggregated number in the footer line will be updated.

Have a look into the layout definition:

<t:beanprocessing id="g_2" >

  <t:beanmethodinvoker id="g_3"

                       actionListener="#{d.demoBeanprocessing.onUpdateGrid}"

                       jsfphase="invokeEnd" />

</t:beanprocessing>

<t:rowtitlebar id="g_4" text="Bean Processing" /><t:rowbodypane id="g_5" rowdistance="2" >

  <t:row id="g_6" >

    <t:fixgrid id="g_7" bordercolor="#C0C0C0" borderheight="1" borderwidth="1"

                        objectbinding="#{d.demoBeanprocessing.rows}"

                        rowheight="16" sbvisibleamount="5" >

      <t:gridcol id="g_8" text="Revenue" width="100" >

        <t:formattedfield id="g_9" align="right" format="int" value=".{revenue}" />

      </t:gridcol>

      <t:gridcol id="g_10" text="Cost" width="100" >

        <t:formattedfield id="g_11" align="right" format="int" value=".{cost}" />

      </t:gridcol>

      <t:gridcol id="g_12" text="Profit" width="100" >

        <t:formattedfield id="g_13" align="right" background="#F0F0F0" enabled="false"

                          format="int" value=".{profit}" />

      </t:gridcol>

      <t:gridfooter id="g_14" >

        <t:formattedfield id="g_15" align="right" background="#E0E0E0" enabled="false"

           format="int" value="#{d.demoBeanprocessing.totalRevenue}" />

        <t:formattedfield id="g_16" align="right" background="#E0E0E0" enabled="false"

           format="int" value="#{d.demoBeanprocessing.totalCost}" />

        <t:formattedfield id="g_17" align="right" background="#E0E0E0" enabled="false"

           format="int" value="#{d.demoBeanprocessing.totalProfit}" />

      </t:gridfooter>

    </t:fixgrid>

  </t:row>

  <t:row id="g_18" >

    <t:button id="g_19" actionListener="#{d.demoBeanprocessing.onCreateRow}"

                        text="Create Row" />

  </t:row>

</t:rowbodypane>

 

The interesting part is the highlighted one: there is the definition that the method “#{d.demoBeanprocessing.onUpdateGrid}” is executed at the end of the invoke phase. This means: whatever happens in the update or in the invoke phase, this method is processed at the end of the invoke phase.

Have a look into the code of the managed bean:

package workplace;

 

import javax.faces.event.ActionEvent;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.elements.impl.FIXGRIDItem;

import org.eclnt.jsfserver.elements.impl.FIXGRIDListBinding;

 

@CCGenClass (expressionBase="#{d.demoBeanprocessing}")

 

public class DemoBeanProcessing extends DemoBase

{

    static int MAX_ROWS = 5;

    

    public DemoBeanProcessing()

    {

        for (int i=0; i<MAX_ROWS; i++)

        {

            MyRow r = new MyRow();

            m_rows.getItems().add(r);

        }

    }

    

    public class MyRow extends FIXGRIDItem implements java.io.Serializable

    {

        protected int m_profit;

        public int getProfit() { return m_profit; }

        public void setProfit(int value) { m_profit = value; }

 

        protected int m_cost;

        public int getCost() { return m_cost; }

        public void setCost(int value) { m_cost = value; }

 

        protected int m_revenue;

        public int getRevenue() { return m_revenue; }

        public void setRevenue(int value) { m_revenue = value; }

 

    }

    

    protected FIXGRIDListBinding<MyRow> m_rows = new FIXGRIDListBinding<MyRow>();

    public FIXGRIDListBinding<MyRow> getRows() { return m_rows; }

    

    protected int m_totalProfit;

    public int getTotalProfit() { return m_totalProfit; }

 

    protected int m_totalCost;

    public int getTotalCost() { return m_totalCost; }

 

    protected int m_totalRevenue;

    public int getTotalRevenue() { return m_totalRevenue; }

 

    public void onUpdateGrid(ActionEvent event)

    {

        m_totalRevenue = 0;

        m_totalCost = 0;

        m_totalProfit = 0;

        for (int i=0; i<MAX_ROWS; i++)

        {

            MyRow r = m_rows.getItems().get(i);

            r.m_profit = r.m_revenue - r.m_cost;

            m_totalRevenue += r.m_revenue;

            m_totalCost += r.m_cost;

            m_totalProfit += r.m_profit;

        }

    }

 

    public void onCreateRow(ActionEvent event)

    {

        MyRow r = new MyRow();

        m_rows.getItems().add(r);

    }

 

}

 

In the method all the calculated numbers are re-calculated.

You see: by using the BEANMETHODINVOKER component you can make sure that a certain processing that is relevant for the user interface processing is always called with a certain request processing phase.

This makes programming in many cases much easier and structured. In the example the calling of the “onUpdateGrid()” method makes sure that the calculated numbers are correct – there is no necessity to make sure in a fine-granular way, that every update of numbers needs to also update the calculated numbers.

Complex Expressions

The CaptainCasa server runtime is based on Java Server Faces (JSF). The expression management (e.g. “#{d.xyz.abc}”) that is part of CaptainCasa is delegated to the expression management of JSF.

In this documentation we only refer to simple expressions:

In general we (strongly) recommend to keep with simple expressions – and at least to be very careful when using more complex expressions, that are provided by the JSF expression management as well.

Exanples for complex expressions are:

There are three reasons:

Expression Replacements

Let's take a look into expression replacements in order to check, what type of complex expressions are possible (though not recommended...!).

Consequence

The first consequence is: keep yourself out of the game of complex expressions!!! You see: this is the only chapter of the documentation mentioning them at all...

If you want to use complex expressions only use these ones...

If you want to use complex expressions: do not use them in the grid column processing for server performance reason.

Managing Date/Time Values

This issue is such important that we spend this own chapter for it. Please note: the management of date/time values is not only a user-interface-issue, but an issue that affects the whole application – including the application logic and the database management!

Controls for defining date/time values

There are the following controls to enter or display date/time values:

All these controls behave the same way when it comes to transferring date/time values into a user-readable representation.

Internal data transfer to/from the controls – Long value + Time zone

Date values are internally passed as long values from the server to the client processing. The long value is the one that represents the Milliseconds from 1st of Jan 1970 on – i.e. the one that is also used within the normal date management in Java.

In order to transfer the internal long value into some visible date the client control needs to know the time zone, that the long-value refers to. All the date/time-components as consequence provide two attributes – the one for passing the date value, the other for passing the time zone:

Binding to Java date/time values

Using java.util.Date

This is the “traditional” way of defining Java-date/time-values:

Example:

<t:calendarfield value=”#{d.XyzUI.start}” timezone=”CET”/>

 

The server side Code looks like:

import java.util.Date;

 

public class XyzUI

{

    ...

    Date m_start;
   public Date getStart() { return m_start; }

    public void setStart(Date value) { m_start= value; }

    ...

}

 

The timezone-value is the normal ISO code of the time zone, that is the one that is also managed within the Java-Timezone-class.

Using long/Long

You can use the straight long/Long values instead of Date-values as well:

<t:calendarfield value=”#{d.XyzUI.start}” timezone=”CET”/>

 

The server side Code looks like:

import java.util.Date;

 

public class XyzUI

{

    ...

    Long m_start;
   public Long getStart() { return m_start; }

    public void setStart(Long value) { m_start= value; }

    ...

}

 

Using LocalDate/LocalDateTime

Since Java 8 there is a set of “new” date/time classes, that unburden the developer from managing time zones. LocalDate/LocalDateTime are not representing a specific point of time at a specific location of the world – but refer to a general business date which is independent from time zones.

CaptainCasa internally transfers the value of LocalDate/LocalDateTime objects as long-value to the client, referring to UTC timezone. Instead of writing “UTC” you simple can use timezone “LOCAL”.

<t:calendarfield value=”#{d.XyzUI.start}” timezone=”LOCAL”/>

 

The server side Code looks like:

public class XyzUI

{

    ...

    LocalDate m_start;
   public LocalDate getStart() { return m_start; }

    public void setStart(LocalDate value) { m_start= value; }

    ...

}

Passing time zone values

You always MUST pass some time zone value into the client side control processing! Also when using LocalDate/Time you need to tell the client by referring to the pseudo-time-zone “LOCAL”.

By each component

This is the “direct” way. You can directly set the time zone within the TIMEZONE attribute:

<t:calendarfield value=”#{d.XyzUI.start}” timezone=”CET”/>

 

Of course you can bind the TIMEZONE-value to some expression:

<t:calendarfield value=”#{d.XyzUI.start}” timezone=”#{d.XyzUI.timeZone}”/>

 

The implementation of the binding is completely up to you:

public class XyzUI

{

    ...

    public String getTimeZone()

    {

        return TimeZone.GMT;

       or:

       return TimeZone.getDefault().getID();

       or:
       return MyApplicationConfig.getApplicationTimeZoneId();

 

        or:

        return “LOCAL”;

 

        or:

        ...

    }

    ...

}

For all components

It is also possible to set the time zone for the whole client centrally within the user-session – instead of doing it within each control definition.

To do so call the API “Client.instance().setTimeZone(...)” within the start-up of a user-dialog-session. A good point of time to do so is the constructor of your Dispatcher-class:

public class Dispatcher()

{

    public Dispatcher()

    {

        Client.instance().setTimezone(...);

    }

}

 

Now you can simply define the controls without explicitly defining the TIMEZONE attribute:

<t:calendarfield value=”#{d.XyzUI.start}”/>

 

If explicitly setting the TIMEZONE attribute this will over-rule the centrally defined value.

Not taking care of the time zone – Pay attention! (!!!)

If not taking care of the time zone at all, then the client will always refer to the time zone that is defined by the browser environment.

Which means: a user in the US will see and enter Date/Time values with a different timezone than the user in Europe or Asia. This typically is only reasonable in very rare cases and typically causes quite some confusion on application processing side.

Example:

During “normal” testing you typically have straight forward situations in which the Java-server is located at the same location as the browser clients. So the time-zone-defaults for date management within the server and the defaults within the client do match. - This “normal” scenario of course needs to be the normal scenario on your users' side, where you in particular do not have any influence on the locale settings within the browser.

Strategies

A strategy how to deal with date/time values is not driven by the user interface – but is some strategy for your complete application! - And: there are many strategies... So the following chapters only are some proposals, which may trigger the discussion on your side.

Use LocalDate/LocalDateTime everywhere

The scenario is:

Proposal:

<t:calendarfield value=”...” /> <= no timezone definition required

 

Client.instance().setTimezone(“LOCAL”);

 

Use java.util.Date and tim zone UTC (or equivalent: GMT) everywhere

The scenario is the same as the previous one, but you are a Java version below Java 8.

Proposal:

<t:calendarfield value=”...” /> <= no timezone definition required

 

Client.instance().setTimezone(“UTC”);

 

Use java.util.Date and explicit time-zones which are centrally defined per user session

The scenario is:

Proposal:

<t:calendarfield value=”...” /> <= no timezone definition required

 

Client.instance().setTimezone(...timezoneOfLogon...);

 

Use/manage explicit time zones on client-side

The scenario is:

Proposal:

<t:calendarfield value=”...” /> <= no timezone definition required

 

Client.instance().setTimezone(...timezoneOfLogon...);

 

<t:calendarfield value=”...” timezone=”...”/>

 

Available time zones

On client side an opens source library (“moment.js” - https://momentjs.com) is used for overcoming JavaScript weaknesses in the area of time zone/calendar management. This library supports the following time zones:

Africa/Abidjan, Africa/Accra, Africa/Addis_Ababa, Africa/Algiers, Africa/Asmara, Africa/Asmera, Africa/Bamako, Africa/Bangui, Africa/Banjul, Africa/Bissau, Africa/Blantyre, Africa/Brazzaville, Africa/Bujumbura, Africa/Cairo, Africa/Casablanca, Africa/Ceuta, Africa/Conakry, Africa/Dakar, Africa/Dar_es_Salaam, Africa/Djibouti, Africa/Douala, Africa/El_Aaiun, Africa/Freetown, Africa/Gaborone, Africa/Harare, Africa/Johannesburg, Africa/Juba, Africa/Kampala, Africa/Khartoum, Africa/Kigali, Africa/Kinshasa, Africa/Lagos, Africa/Libreville, Africa/Lome, Africa/Luanda, Africa/Lubumbashi, Africa/Lusaka, Africa/Malabo, Africa/Maputo, Africa/Maseru, Africa/Mbabane, Africa/Mogadishu, Africa/Monrovia, Africa/Nairobi, Africa/Ndjamena, Africa/Niamey, Africa/Nouakchott, Africa/Ouagadougou, Africa/Porto-Novo, Africa/Sao_Tome, Africa/Timbuktu, Africa/Tripoli, Africa/Tunis, Africa/Windhoek, America/Adak, America/Anchorage, America/Anguilla, America/Antigua, America/Araguaina, America/Argentina/Buenos_Aires, America/Argentina/Catamarca, America/Argentina/ComodRivadavia, America/Argentina/Cordoba, America/Argentina/Jujuy, America/Argentina/La_Rioja, America/Argentina/Mendoza, America/Argentina/Rio_Gallegos, America/Argentina/Salta, America/Argentina/San_Juan, America/Argentina/San_Luis, America/Argentina/Tucuman, America/Argentina/Ushuaia, America/Aruba, America/Asuncion, America/Atikokan, America/Atka, America/Bahia, America/Bahia_Banderas, America/Barbados, America/Belem, America/Belize, America/Blanc-Sablon, America/Boa_Vista, America/Bogota, America/Boise, America/Buenos_Aires, America/Cambridge_Bay, America/Campo_Grande, America/Cancun, America/Caracas, America/Catamarca, America/Cayenne, America/Cayman, America/Chicago, America/Chihuahua, America/Coral_Harbour, America/Cordoba, America/Costa_Rica, America/Creston, America/Cuiaba, America/Curacao, America/Danmarkshavn, America/Dawson, America/Dawson_Creek, America/Denver, America/Detroit, America/Dominica, America/Edmonton, America/Eirunepe, America/El_Salvador, America/Ensenada, America/Fort_Nelson, America/Fort_Wayne, America/Fortaleza, America/Glace_Bay, America/Godthab, America/Goose_Bay, America/Grand_Turk, America/Grenada, America/Guadeloupe, America/Guatemala, America/Guayaquil, America/Guyana, America/Halifax, America/Havana, America/Hermosillo, America/Indiana/Indianapolis, America/Indiana/Knox, America/Indiana/Marengo, America/Indiana/Petersburg, America/Indiana/Tell_City, America/Indiana/Vevay, America/Indiana/Vincennes, America/Indiana/Winamac, America/Indianapolis, America/Inuvik, America/Iqaluit, America/Jamaica, America/Jujuy, America/Juneau, America/Kentucky/Louisville, America/Kentucky/Monticello, America/Knox_IN, America/Kralendijk, America/La_Paz, America/Lima, America/Los_Angeles, America/Louisville, America/Lower_Princes, America/Maceio, America/Managua, America/Manaus, America/Marigot, America/Martinique, America/Matamoros, America/Mazatlan, America/Mendoza, America/Menominee, America/Merida, America/Metlakatla, America/Mexico_City, America/Miquelon, America/Moncton, America/Monterrey, America/Montevideo, America/Montreal, America/Montserrat, America/Nassau, America/New_York, America/Nipigon, America/Nome, America/Noronha, America/North_Dakota/Beulah, America/North_Dakota/Center, America/North_Dakota/New_Salem, America/Ojinaga, America/Panama, America/Pangnirtung, America/Paramaribo, America/Phoenix, America/Port-au-Prince, America/Port_of_Spain, America/Porto_Acre, America/Porto_Velho, America/Puerto_Rico, America/Rainy_River, America/Rankin_Inlet, America/Recife, America/Regina, America/Resolute, America/Rio_Branco, America/Rosario, America/Santa_Isabel, America/Santarem, America/Santiago, America/Santo_Domingo, America/Sao_Paulo, America/Scoresbysund, America/Shiprock, America/Sitka, America/St_Barthelemy, America/St_Johns, America/St_Kitts, America/St_Lucia, America/St_Thomas, America/St_Vincent, America/Swift_Current, America/Tegucigalpa, America/Thule, America/Thunder_Bay, America/Tijuana, America/Toronto, America/Tortola, America/Vancouver, America/Virgin, America/Whitehorse, America/Winnipeg, America/Yakutat, America/Yellowknife, Antarctica/Casey, Antarctica/Davis, Antarctica/DumontDUrville, Antarctica/Macquarie, Antarctica/Mawson, Antarctica/McMurdo, Antarctica/Palmer, Antarctica/Rothera, Antarctica/South_Pole, Antarctica/Syowa, Antarctica/Troll, Antarctica/Vostok, Arctic/Longyearbyen, Asia/Aden, Asia/Almaty, Asia/Amman, Asia/Anadyr, Asia/Aqtau, Asia/Aqtobe, Asia/Ashgabat, Asia/Ashkhabad, Asia/Baghdad, Asia/Bahrain, Asia/Baku, Asia/Bangkok, Asia/Barnaul, Asia/Beirut, Asia/Bishkek, Asia/Brunei, Asia/Calcutta, Asia/Chita, Asia/Choibalsan, Asia/Chongqing, Asia/Chungking, Asia/Colombo, Asia/Dacca, Asia/Damascus, Asia/Dhaka, Asia/Dili, Asia/Dubai, Asia/Dushanbe, Asia/Gaza, Asia/Harbin, Asia/Hebron, Asia/Ho_Chi_Minh, Asia/Hong_Kong, Asia/Hovd, Asia/Irkutsk, Asia/Istanbul, Asia/Jakarta, Asia/Jayapura, Asia/Jerusalem, Asia/Kabul, Asia/Kamchatka, Asia/Karachi, Asia/Kashgar, Asia/Kathmandu, Asia/Katmandu, Asia/Khandyga, Asia/Kolkata, Asia/Krasnoyarsk, Asia/Kuala_Lumpur, Asia/Kuching, Asia/Kuwait, Asia/Macao, Asia/Macau, Asia/Magadan, Asia/Makassar, Asia/Manila, Asia/Muscat, Asia/Nicosia, Asia/Novokuznetsk, Asia/Novosibirsk, Asia/Omsk, Asia/Oral, Asia/Phnom_Penh, Asia/Pontianak, Asia/Pyongyang, Asia/Qatar, Asia/Qyzylorda, Asia/Rangoon, Asia/Riyadh, Asia/Saigon, Asia/Sakhalin, Asia/Samarkand, Asia/Seoul, Asia/Shanghai, Asia/Singapore, Asia/Srednekolymsk, Asia/Taipei, Asia/Tashkent, Asia/Tbilisi, Asia/Tehran, Asia/Tel_Aviv, Asia/Thimbu, Asia/Thimphu, Asia/Tokyo, Asia/Tomsk, Asia/Ujung_Pandang, Asia/Ulaanbaatar, Asia/Ulan_Bator, Asia/Urumqi, Asia/Ust-Nera, Asia/Vientiane, Asia/Vladivostok, Asia/Yakutsk, Asia/Yekaterinburg, Asia/Yerevan, Atlantic/Azores, Atlantic/Bermuda, Atlantic/Canary, Atlantic/Cape_Verde, Atlantic/Faeroe, Atlantic/Faroe, Atlantic/Jan_Mayen, Atlantic/Madeira, Atlantic/Reykjavik, Atlantic/South_Georgia, Atlantic/St_Helena, Atlantic/Stanley, Australia/ACT, Australia/Adelaide, Australia/Brisbane, Australia/Broken_Hill, Australia/Canberra, Australia/Currie, Australia/Darwin, Australia/Eucla, Australia/Hobart, Australia/LHI, Australia/Lindeman, Australia/Lord_Howe, Australia/Melbourne, Australia/NSW, Australia/North, Australia/Perth, Australia/Queensland, Australia/South, Australia/Sydney, Australia/Tasmania, Australia/Victoria, Australia/West, Australia/Yancowinna, Brazil/Acre, Brazil/DeNoronha, Brazil/East, Brazil/West, CET, CST6CDT, Canada/Atlantic, Canada/Central, Canada/East-Saskatchewan, Canada/Eastern, Canada/Mountain, Canada/Newfoundland, Canada/Pacific, Canada/Saskatchewan, Canada/Yukon, Chile/Continental, Chile/EasterIsland, Cuba, EET, EST, EST5EDT, Egypt, Eire, Etc/GMT, Etc/GMT+0, Etc/GMT+1, Etc/GMT+10, Etc/GMT+11, Etc/GMT+12, Etc/GMT+2, Etc/GMT+3, Etc/GMT+4, Etc/GMT+5, Etc/GMT+6, Etc/GMT+7, Etc/GMT+8, Etc/GMT+9, Etc/GMT-0, Etc/GMT-1, Etc/GMT-10, Etc/GMT-11, Etc/GMT-12, Etc/GMT-13, Etc/GMT-14, Etc/GMT-2, Etc/GMT-3, Etc/GMT-4, Etc/GMT-5, Etc/GMT-6, Etc/GMT-7, Etc/GMT-8, Etc/GMT-9, Etc/GMT0, Etc/Greenwich, Etc/UCT, Etc/UTC, Etc/Universal, Etc/Zulu, Europe/Amsterdam, Europe/Andorra, Europe/Astrakhan, Europe/Athens, Europe/Belfast, Europe/Belgrade, Europe/Berlin, Europe/Bratislava, Europe/Brussels, Europe/Bucharest, Europe/Budapest, Europe/Busingen, Europe/Chisinau, Europe/Copenhagen, Europe/Dublin, Europe/Gibraltar, Europe/Guernsey, Europe/Helsinki, Europe/Isle_of_Man, Europe/Istanbul, Europe/Jersey, Europe/Kaliningrad, Europe/Kiev, Europe/Kirov, Europe/Lisbon, Europe/Ljubljana, Europe/London, Europe/Luxembourg, Europe/Madrid, Europe/Malta, Europe/Mariehamn, Europe/Minsk, Europe/Monaco, Europe/Moscow, Europe/Nicosia, Europe/Oslo, Europe/Paris, Europe/Podgorica, Europe/Prague, Europe/Riga, Europe/Rome, Europe/Samara, Europe/San_Marino, Europe/Sarajevo, Europe/Simferopol, Europe/Skopje, Europe/Sofia, Europe/Stockholm, Europe/Tallinn, Europe/Tirane, Europe/Tiraspol, Europe/Ulyanovsk, Europe/Uzhgorod, Europe/Vaduz, Europe/Vatican, Europe/Vienna, Europe/Vilnius, Europe/Volgograd, Europe/Warsaw, Europe/Zagreb, Europe/Zaporozhye, Europe/Zurich, GB, GB-Eire, GMT, GMT+0, GMT-0, GMT0, Greenwich, HST, Hongkong, Iceland, Indian/Antananarivo, Indian/Chagos, Indian/Christmas, Indian/Cocos, Indian/Comoro, Indian/Kerguelen, Indian/Mahe, Indian/Maldives, Indian/Mauritius, Indian/Mayotte, Indian/Reunion, Iran, Israel, Jamaica, Japan, Kwajalein, Libya, MET, MST, MST7MDT, Mexico/BajaNorte, Mexico/BajaSur, Mexico/General, NZ, NZ-CHAT, Navajo, PRC, PST8PDT, Pacific/Apia, Pacific/Auckland, Pacific/Bougainville, Pacific/Chatham, Pacific/Chuuk, Pacific/Easter, Pacific/Efate, Pacific/Enderbury, Pacific/Fakaofo, Pacific/Fiji, Pacific/Funafuti, Pacific/Galapagos, Pacific/Gambier, Pacific/Guadalcanal, Pacific/Guam, Pacific/Honolulu, Pacific/Johnston, Pacific/Kiritimati, Pacific/Kosrae, Pacific/Kwajalein, Pacific/Majuro, Pacific/Marquesas, Pacific/Midway, Pacific/Nauru, Pacific/Niue, Pacific/Norfolk, Pacific/Noumea, Pacific/Pago_Pago, Pacific/Palau, Pacific/Pitcairn, Pacific/Pohnpei, Pacific/Ponape, Pacific/Port_Moresby, Pacific/Rarotonga, Pacific/Saipan, Pacific/Samoa, Pacific/Tahiti, Pacific/Tarawa, Pacific/Tongatapu, Pacific/Truk, Pacific/Wake, Pacific/Wallis, Pacific/Yap, Poland, Portugal, ROC, ROK, Singapore, Turkey, UCT, US/Alaska, US/Aleutian, US/Arizona, US/Central, US/East-Indiana, US/Eastern, US/Hawaii, US/Indiana-Starke, US/Michigan, US/Mountain, US/Pacific, US/Pacific-New, US/Samoa, UTC, Universal, W-SU, WET, Zulu

 

Please check if the time zone that you are using is part of the list of available time zones.

Session Management

A dialog on the client side is bound to some managed bean on server side – that's the basics of all interaction processing within CaptainCasa Enterprise Client. This chapter tells about the organization of beans on server side – and the integration into the http session management.

Basics

A user may open one or several browser instances (e.g. several browser tabs) in which he/she opens up CaptainCasa dialogs

The organization of beans guarantees that each dialog instance is talking to its corresponding server side bean.

Two ways

The base of all organization is the http-session management which is part of the server side servlet engine (e.g. Tomcat). This http-session management can be used in two ways.

Way 1: Session Management by URL-encoding

This is the “original way” that CaptainCasa uses from its beginning on: the session management is done by using URL-encoding: each URL that is transferred from the server to the client is providing a session information (e.g. “http://......;jsessionid=....”), so that the session is correctly resolved when the client talks back to the server.

The session is built up on server side when the first URL of the dialog hits the server, and from then is kept due to the encoding information within the URL.

In this scenario each dialog instance (browser or browser tab) that is started on the client side resided in its own http session on server side.


The managed beans are resolved in a default way on server side within a session. i.e. if an expression is resolved then the expression is analyzed and resolved using e.g. faces-config.xml as definition point for root expressions.

Advantages

Disadvantages

Way 2: Session Management by COOKIEs

Because of the disadvantages of the URL-encoding we introduced an updated way of session management with Update 20180416.

Now cookies are used for session tracking (it's the “JSESSIONID” cookie we are talking about). Everyone that uses cookies knows that cookies are used across multiple browser instances, so that a cookie-based session is shared across multiple browser instances of one user:

So how to we separate dialog sessions now?

The CaptainCasa JavaScript client is a single page application that talks via “AJAX-requests” to the server side. When starting an instance of this client within a browser page, each instance (the white boxes in the diagram) receives some client-unique identification. This identification, internally referenced as “subpageId” is sent with every communication from the client to the server.

On server side we use some own expression resolver – which is a usage of the default extension interface that is part of Java Server Faces. This means, whenever an expression is resolved (“#{d.Xxxx.Yyyy}”), then it's the own expression resolver that is used.

This expression resolver now checks for the “subpageId” that is available within each request processing – and guarantees that all the root expression objects (e.g. “#{d}”) are resolved as separate instances for each “subpageId”.

So the combination out of...

...ensures that the managed beans are properly resolved on server side.

Advantages

Disadvantages

Configuration

The configuration is done by two files:

For both files there are template files in the webcontentcc-part of the project.

The configuration of “Way 1: Session Management by URL-encoding” is:

web.xml:

 

...

  <session-config>

      ...

      <tracking-mode>URL</tracking-mode>

      ...

  </session-config>

...

 

system.xml:

 

...

    <sessionmanagement type="URL"/>

...

 

The configuration of “Way 2: Session Management by COOKIEs” is:

web.xml:

 

...

  <session-config>

      ...

      <tracking-mode>COOKIE</tracking-mode>

      ...

  </session-config>

...

 

system.xml:

 

...

    <sessionmanagement type="COOKIE"/>

...

 

Storing data in session context

There are situations in which you want to store certain objects within the session context. You may compare this to the use of global variables which are bound to one session context.

Accessing the http-session

The http-session is available by calling the function:

HttpSession session = HttpSessionAccess.getCurrentHttpSession();

...

session.setAttribute(“...”,...);

...

Object o = session.getAttribute(“...”);

...

 

If working in the scenario “Way1” (URL), then there is one http session for each dialog. So you may store data of this dialog instance there as well.

If working in the scenario “Way2” (COOKIE), then one http session is or might be shared across multiple dialog instances (browser tabs). In this case you must only use the http-session for storing information that really is shared between all of the dialog instances.

Accessing the dialog-session - explicit

If working with “Way2” (COOKIE) then there is an additional session context available, which is called “SubpageContext”. This context is arranged below the http-session – and there is exactly one context per dialog instance (browser/browser tab).

This dialog context is available by the function:

SubpageContext spc = HttpSessionAccess.getCurrentSubpageContext();

...

spc.setAttribute(“...”,...);

...

Object o = spc.getAttribute(“...”);

...

 

You see: the “parking” of information is done through the same methods, but now on a different object.

Accessing dialog-session – abstracted

There is an abstracted interface that allows to just call...:

IsessionAbstraction dialogSession = HttpSessionAccess.getCurrentDialogSession();

...

dialogSession.setAttribute(“...”,...);

...

Object o = dialogSession.getAttribute(“...”);

...

 

The HttpSessionAccess-function decides – dependent on the configuration of the system – if the session that is returned is an http-session or if it is a subpage-Context.

This is the recommended way of picking a dialog session, because it is compatible for both ways of session configuration.

Best practices when storing data in http-/dialog-session

Both http-session and dialog-session can be used like maps – you can store any object with a certain key value. As consequence it is important to properly track access to these objects – and not to allow “everyone” to store “any” content.

It typically is a good idea to define one structured object, in which you keep all the data you want to store and access this object centrally, so that you can properly track access/manipulations.

Example:

public class MyDialogSessionContext

{

    static final KEY = MyDialogSessionContext.class.getName();

 

    public static MyDialogSessionContext getInstance()

    {

        MyDialogSessionContext result = (MyDialogSessionContext)
           HttpSessionAccess.getCurrentDialogSession()

            .getAttribute(KEY);

        if (result == null)
       {

            result = new MyDialogSessionContext();

            HTTPSessionAccess.getCurrentDialogSession()

            .setAttribute(KEY,result);

        }

        return result;

    }

 

    // all data you want to keep

    String m_user;

    String m_language;

    …

    …

}

Life-cycle of sessions

Starting a session is always simple: it is created when it is required. ;-)

So, closing a session is much more interesting!

Control SESSIONCLOSER

There is an invisible control SESSIONCLOSER (unter BEANPROCESSING) that you may arrange in the outest page (!) of your application. The SESSIONCLOSER sends some signal to the server side when the user closes the client.

Closing the client can be done by...:

The serve-side processing of the SESSIONCLOSER component depends on the session configuration:

Time out

If not explicitly closed then session are timed out by the servlet engine. The configuration of the time out is done in web.xml:

  <session-config>
     ...

      <session-timeout>60</session-timeout>

      ...

  </session-config>

 

Invalidating the session

Both http-session and subpage-context provide an “invalidate” method.

The typical reaction on client side after invalidation is that the start-URL is reloaded – and that the user is brought back to the start page as consequence.

If you want to invalidate the current dialog session independent from the session configuration (URL/COOKIE), then use the following function:


HttpSessionAccess.getCurrentDialogSession().invalidate();

 

Dependent on the configuration this will either invalidate the http-session (URL-scenario) or the subpage-context (COOKIE-scenario).

Reacting on invalidation of session

You may want to clean up resources when a session is closed. In this case you need to listen to the closing of the session.

Reacting on closing of current dialog session

The method...

HttpSessionAccess.getCurrentDialogSession().addListener(...)

 

...provides the possibility to register an implementation of “IsessionAbstractionListener”:

public interface ISessionAbstractionListener

{

    public void reactOnClosed();

}

 

This method is called at the following point of time:

Reacting on closing the session - http-session

Independent from the listener on dialog-session level, there is a listener on http-session level:

HttpSessionAccess.getCurrentHttpSessionListenerDelegator().addReactor

(

    “...”, // name of listener

    listener, // instance of IHttpSessionClosedReactor

);

 

The interface IhttpSessionClosedReactor is:

public interface IHttpSessionClosedReactor

{

    public void reactOnClosed();

}

 

This interface may be both interesting in the “Way1” (URL) and “Way2” (COOKIE) scenario.

Reacting on closing the session – Dispatcher level

Each dispatcher instance has a “destroy” method. This method is called when the corresponding dispatcher instance is closed:

When using the workplace framework then there is a “sub-dispatcher” created per workpage instance:

Because the same Dispatcher-class is used both for the root-dispatcher and for the workpage-dispatchers you have to find out which level your concrete dispatcher instances has when overriding the destroy()-method:

public class Dispatcher extends WorkpageDispatcher

{

    public void destroy()

    {

        if (getOwner() == null)

        {

            // this is the rooot dispatcher of the dialog session

        }

        else

        {

            // this is the dispatcher of the workpage

        }

        suoer.destroy();

    }

}

 

Instead of overriding the destroy() method you may also use an other mechanism: each WorkpageDispatcher instance is bound to a Workpage instance. And a Workpage instance also has a life-cycle listener:

workpage.addLifeCycleListener(...);

 

 

public interface IWorkpageLifecycleListener

{

    ...

    public void reactOnDestroyed();

    ...

}

 

The life-cycle listeners of the workpage are called by the default destroy()-processing of the corresponding WorkpageDispatcher instance.

 

For “Way 1” (URL) users – What to do

All CaptainCasa Enterprise Client use cases are of type “Way 1” (URL) up to update 20180416.

There are several options for you to go:

Things to check / update

The main difference between the URL-way and the COOKIE-way is that there is some layering between the http-session (which might be spanned across multiple browser instances) and the dialog-session (which exactly represents one browser instance).

Consequence: you need to check all the statements in your code in which you access the session context – and decide, whether you really want to access the http-session context or if you want to access the dialog-session context.

This means:

From:

HttpSession session = HttpSessionAccess.getCurrentHttpSession();

 

To:

IsessionAbstraction session = HttpSessionAccess.getCurrentDialogSession();

Especially: check your invalidate() calls!

Typically you first are checking these places in your code where you access the session/dialog context for storing data (i.e. you use the “setAttribute(...)” and “getAttribute(...)” method).

Please do not forget to also check all occurances of the “.invalidate()” call. If running in COOKIE-mode then the invalidation of an http-session will not only affect the current dialog the user is working in, but it will affect all dialogs that are opened by one user for your application!

Check your webcontent/META-INF/context.xml file

With older projects the context.xml file in the META-INF directory did contain information to not use cookies.

Example for some old and wrong! content:

<?xml version='1.0' encoding='UTF-8'?>

<Context cookies='false'>

</Context>

 

Please update the file to look like:

<?xml version='1.0' encoding='UTF-8'?>

<Context>

</Context>

Which way should be your standard way?

Despite the fact that you should program your application in a way that supports both ways (which is easily possible due to “HttpSessionAccess.getCurrentDialogSession()”!), you have to decide for one default way to deliver your application.

Our recommendation / strategy here is:

API Facade “HttpSessionAccess”

The class “HttpSessionAccess” (package “org.eclnt.jsfserver.util”) is a central Facade for accessing a lot of information that is available in the are of session management.

Prominent methods to use are:

Please check the Javadoc-documentation for more information.

Managing Web Resources

The resources that we are talking about here, are the resources that are required to build the layout content of a dialog. These include:

Classical way: in web-content

Up to version 20200825 CaptainCasa followed the classical way of storing these resources: resources were part of the web-content.

Location in projects

Example: when following the standard CaptainCasa project layout then there is a “webcontent” directory in which the resources are kept:

<project-directory>

    /src

    /webcontent

        /images

            /icons

                back.svg

                save.svg

        /pages

            outest.jsp

            logon.jsp

    /webcontentbuild

    /webcontentcc

 

Example: when following the Maven project layout thent there is a “webapp” directory in which the resources are kept:

<project-directory>

    /src

        /main

            /java

            /resources

            /webapp

                /images

                    /icons

                        back.svg

                        save.svg

                /pages

                    outest.jsp

                    logon.jsp

    ...

 

Referencing resources

Resource files are referenced from the web-content directory's perspective.

Example: “/images/icons/back.svg” points to the image file that is shown in the previous text.

Alternative way: in classes

Since version 20200825 you can store resources in the classes as well – so that they are loaded by the Java classloader.

Location in projects

Example: When following the standard CaptainCasa project layout then there is a “src” directory in which the all Java-class related issues are stored. Now you can also store the resources within the directory being part of any package that you select:

<project-directory>

    /src

        /com

            /xyz

                /images

                    /icons

                        back.svg

                        save.svg

                /pages

                    outest.jsp

                    logon.jsp

    /webcontent

    /webcontentbuild

    /webcontentcc

 

Example: when following the Maven project layout then there is a “resources” directory in which the Java-related resources are kept:

<project-directory>

    /src

        /main

            /java

            /resources

                /com

                    /xyz

                        /images

                            /icons

                                back.svg

                                save.svg

                        /pages

                            outest.jsp

                            logon.jsp

            /webapp

    ...

 

In both examples the resources are part of the Java compilation/build process and are stored e.g. in some /classes file or in some .jar file as result.

Referencing resources

Resource files are referenced with their full package name and resource name:

Example: “/com/xyz/images/icons/back.svg” points to the image file that is shown in the previous text.

Comparison

In general

From development and logical runtime point of view both ways are the same: you store resources “somewhere” and you reference them. - There is no significant difference in quality or speed of access.

Separation of one project into sub-projects

The difference comes when working in projects which are separated into diverse sub-projects.

Example: in a project you may want to separate some base dialogs (logon, workplace, ...) into “base”-project and you may want to have some own project for each area of the application (“masterdata”-project, ...).

When working with “web-content” based way of storing resources then the integration of projects always means that you have to...

When working with “classes” based way of storing resources then there is only one step – you only have to include the jar-files. The integration is much easier and feels much more “natural”.

Coexistence and Transfer

Coexistence

Both ways of storing resources may coexist within one project. You need to make sure that a resource with a certain reference (e.g. “/images/icons/back.svg”) is either located in the web-content or is located in the classes.

In case of one resource being located at both locations, the “classes”-location is accessed first.

Transfer

If you worked so far with “web-content”-managed resources then it is very easy to switch over to the “classes”-managed resources: you just have to copy over all the resources from the webcontent directory into the Java-sources directory (or the Java-resources directory) – that's it.

All the references are still the same – and due to the internal sequence of access (“classes”-location accessed first) you can be sure about, that your “webcontent”-based resources are no longer used.

Recommendation

If there is only one project – then things are simple. You may use both ways – there is no great advantage/disadvantage for one of the ways.

If there is a structure of different projects forming one big project at the end – then we clearly recommend to go the “classes”-way.

Integration within CaptainCasa Toolset

In the layout editor the two ways of managing resources are reflected by providing the selection between “Web content” and “Sources” at various places.

Project overview

You may either create layouts in the “Web content” section or in the “Source” section. The tree structure below this selection adapts correspondingly.

SVG icon manager

You may store the icons either in the web content or in the sources.

Resource selection

When opening the resource selection e.g. for image-attributes then there is a corresponding selection between “Web content” and “Sources”.

Security

Please be aware of the fact that all resources which are part of your code are accessible by a web-reference.

Switching off “classes” resource access

You may switch off the “classes”-way of accessing resources by the following configuration in “/eclntjsfserver/config/system.xml”:

<system ...>

    ...

    <resourceclassloaderaccess active="false"/>

    ...

</system>

Configuration of extensions

Resources are identified by their extension. The following extensions are the default ones that are classified to be resources:

*.bmp

*.gif

*.giff

*.htm

*.html

*.jpg

*.jpeg

*.pdf

*.png

*.svg

*.tif

*.tiff

 

You may extend this list by editing “/eclntjsferver/config/system.xml”:

<system ...>

    ...

    <resourceclassloaderaccess additionalextensions="doc;xls;docx;xlsx"/>

    ...

</system>

 

Adaptive User Interfaces

Scaling the Screen

Via URL-Parameter

The basic size definitions within a layout are either pixel-based or percentage-based. The size of a physical pixel may of course differ from device to device. As consequence there is some simple possibility to scale the whole dialog – by appending “ccscale=xx” to the URL that starts the application.

The same screen is started first with its normal size, then with a scale of “1.5”:

The “ccscale=xx” definition is a query parameter, i.e. it is appended to the URL with “?ccscale=xx” if it is the only parameter, or it is appended with “&ccscale=xx” if it is following another parameter.

Typically it makes sense to set the parameter for tablet and mobile phone scenarios.

Via CLIENTCONFIG Component

The same scale parameter can also be set by using the CLIENTCONFIG component – and using the SCALE attribute there. If setting the parameter via CLIENTCONFOG then this will override the definition of the URL-parameter.

Adaptive Containers

Within the Demo Workplace there is a separate chapter “Container Controls > Adaptive Containers”. Please check the examples there in addition to reading this text.

ROWFLEXLINECONTAINER – A Row with Line Breaks

The special row component ROWFLEXLINECONTAINER allows to arrange a sequence of contained controls. The controls are horizontally listed – one after the other. If there is no sufficient horizontal space, then a “line break” is done, and the controls are arranged in a second (or third or fourth...) line.

In the following example the images are arranged in two lines...



...or in three lines...



...dependent on the size of the screen.

The corresponding XML is:

<t:rowflexlinecontainer id="g_4" coldistance="10" rowdistance="10">0

    <t:image id="g_5" height="100" image="/images/pictures/photo0.gif"

        keepratio="false" width="100" />

    <t:image id="g_6" height="100" image="/images/pictures/photo1.gif"

        keepratio="false" width="100" />

    <t:image id="g_7" height="100" image="/images/pictures/photo2.gif"

        keepratio="false" width="100" />

    <t:image id="g_8" height="100" image="/images/pictures/photo4.gif"

        keepratio="false" width="100" />

    <t:image id="g_9" height="100" image="/images/pictures/photo5.gif"

        keepratio="false" width="100" />

    <t:image id="g_10" height="100" image="/images/pictures/photo6.gif"

        keepratio="false" width="100" />

    <t:image id="g_11" height="100" image="/images/pictures/photo0.gif"

        keepratio="false" width="100" />

    <t:image id="g_12" height="100" image="/images/pictures/photo1.gif"

        keepratio="false" width="100" />

    <t:image id="g_13" height="100" image="/images/pictures/photo2.gif"

        keepratio="false" width="100" />

    <t:image id="g_14" height="100" image="/images/pictures/photo4.gif"

        keepratio="false" width="100" />

</t:rowflexlinecontainer>

 

Please note: all components that are defined in the row should not be defined with a percentage width or height definition!

ROWFLEXCOLUMNCONTAINER – the “rotated” ROWFLEXLINECONTAINER

While the ROWFLEXLINECONTAINER arranges its content row by row, the ROWFLEXCOLUMNCONTAINER arranges its content column by column.

Example: the following content is filling up 2 ½ columns for a certain size:

When the size is decreased then the content is rearranged correspondingly, so that more columns are filled:

The programming of the ROWFLEXCOLUMNCONTAINER is exactly the same as the programming of the ROWFLEXLINECONTAINER.

ROWADAPTIVEAREA – One container, multiple ways of arranging its content

The ROWADPATIVEAREA is a very generically usable component: it holds any number of items (ROWADAPTIVEAREAITEM component), which it arranges to defined rules.

Within the ROWADAPTIVEARAE there is an attribute ADAPTIVECATEGORIES which is used to define certain categories of width definitions.

Example: the definition “narrow:400;normal:800;wide:1200” defines three width categories:

Within the ROWADAPTIVEAREA component you define any number of ROWADAPTIVEAREAITEM components, which themselves are “normal” container components.

The ROWADAPTIVEAREAITEM component provides two important attributes:

As result you may define scenarios like the following:


The three items of the scenario are arranged in a different way – dependent from the size that the ROWADAPTIVEAREA obtains.

ROWADAPTIVELINE – A row with a defined break

This component is a quite simple – but a quite useful one: it represents a row, containing any number of contained items. The row provides two attributes:

The following definition...

<t:rowadaptiveline id="g_2" breakindex="1" breakpixels="300" >

  <t:label id="g_3" text="First name" width="100" >

  </t:label>

  <t:field id="g_4" width="100%" >

  </t:field>

</t:rowadaptiveline>

 

...represents a scenario in which the line is broken in front of the FIELD if the available horizontal space is lower than 300.

Getting to know the Client Sizing

There is a special control which you can embed into any PANE component and which reports the actual current size of the corresponding PANE to the server side. The component's name is SIZETRANSFER:

PANE
   SIZETRANSFER SIZEVALUE='#{...}'
   ROW

        ...
   ROW
   …

 

The size of the PANE is passes into the attribute SIZETRANSFER-SIZEVALUE. There are two ways to pass the sizes:


horizontalcategories="narrow:400;normal:800;wide:1200"

 

Please note: the SIZEVALUE is always set by the client when the size changes (e.g. due to the user resizing the screen, or due to other components changing their size). It does by default not trigger a round trip to the server-side! Like e.g. a normal field, it keeps its values on client side until any other event causes a server side round trip. As with other controls there is the attribute FLUSH: is you set the attribute to “true” then size changes are transferred immediately.

 

 

Dynamic Page Layout

When working with the CaptainCasa Enterprise Client toolset then the normal sequence of operations is:

The advantage of working this way is: the layout definition is done in a declarative way and it is kept outside the bean processing.

There are situations as well, in which it is difficult to completely specify a layout in a static way, because the layout depends on some logic.

Overview

Within a normal page you may define “dynamic areas” which allow to add the components dynamically. A “dynamic area” is represented by some component which binds to some Java processing.

There are two components which are identical from their processing side and which are just used at different places within a layout definition:

ROWDYNAMICCONTENT Component

The easiest way to embed dynamic layout into a page is the ROWDYNAMICCONTENT component. The component provides an attribute CONTENTBINDING which points to a server side property.

Example:

<t:row id="g_2">

    <t:label id="g_3" width="120" />

    <t:label id="g_4" text="Change Content" width="120" />

    <t:button id="g_5"

        actionListener="#{d.DemoRowDynamicContent.onFlip}" text="Flip" />

</t:row>

<t:rowdistance id="g_6" height="20" />

<t:rowline id="g_7" />

<t:rowdistance id="g_8" height="20" />

 

<t:rowdynamiccontent id="g_9" contentbinding="#{d.DemoRowDynamicContent.content}" />

 

<t:row id="g_10">

    <t:label id="g_11" width="120" />

    <t:label id="g_12" text="Change Content" width="120" />

    <t:button id="g_13"

        actionListener="#{d.DemoRowDynamicContent.onFlip}" text="Flip" />

</t:row>

 

In the layout there is a “dynamic row” opened via RODYNAMICCONTENT, the CONTENTBINDING attribute points to a certain managed bean property.

There are two ways of passing the dynamic layout into this property:

The server side implementation shows both ways of usage:

public class DemoRowDynamicContent

   ...

{

    

    protected ROWDYNAMICCONTENTBinding m_content = new ROWDYNAMICCONTENTBinding();

    

    String m_firstName = "First";

    String m_lastName = "Last";

    

    boolean m_toggle = false;

    

 

    public DemoRowDynamicContent(IWorkpageDispatcher dispatcher)

    {

        super(dispatcher);

        renderDynamically();

    }

    

    public ROWDYNAMICCONTENTBinding getContent() { return m_content; }

    

    public void setFirstName(String value) { m_firstName = value; }

    public String getFirstName() { return m_firstName; }

    

    public void setLastName(String value) { m_lastName = value; }

    public String getLastName() { return m_lastName; }

    

    public void onOK(ActionEvent event)

    {

        m_firstName = m_firstName + " OK";

        m_lastName = m_lastName + " OK";

    }

    

    public void onFlip(ActionEvent event)

    {

        m_toggle = !m_toggle;

        renderDynamically();

    }

    

    private void renderDynamically()

    {

        if (m_toggle)

        {

            // dynamic content via object tree

            ComponentNode pane = new ComponentNode("t:pane");

            pane.addAttribute("rowdistance","5");

            {

                ComponentNode row = new ComponentNode("t:row");

                pane.addSubNode(row);

                {

                    ComponentNode label = new ComponentNode("t:label");

                    row.addSubNode(label);

                    label.addAttribute("text","First Name");

                    label.addAttribute("width","120");

                    ComponentNode field = new ComponentNode("t:field");

                    row.addSubNode(field);

                    field.addAttribute("text",

                                       "#{d.DemoRowDynamicContent.firstName}");

                    field.addAttribute("width","120");

                }

            }

            {

                ComponentNode row = new ComponentNode("t:row");

                pane.addSubNode(row);

                {

                    ComponentNode label = new ComponentNode("t:label");

                    row.addSubNode(label);

                    label.addAttribute("text","Last Name");

                    label.addAttribute("width","120");

                    ComponentNode field = new ComponentNode("t:field");

                    row.addSubNode(field);

                    field.addAttribute("text",

                                       "#{d.DemoRowDynamicContent.lastName}");

                    field.addAttribute("width","120");

                }

            }

            {

                ComponentNode row = new ComponentNode("t:row");

                pane.addSubNode(row);

                {

                    ComponentNode distance = new ComponentNode("t:coldistance");

                    row.addSubNode(distance);

                    distance.addAttribute("width","120");

                    ComponentNode button = new ComponentNode("t:button");

                    row.addSubNode(button);

                    button.addAttribute("text","OK");

                    button.addAttribute("actionListener",

                                        "#{d.DemoRowDynamicContent.onOK}");

                }

            }

            m_content.setContentNode(pane);        }

        else

        {

            m_content.setContentXml

            (

                "<t:pane rowdistance='5'>" +

                "<t:row>" +

                "<t:label text='First/Last Name' width='120'/>" +

                "<t:field text='#{d.DemoRowDynamicContent.firstName}' width='120'/>" +

                "<t:field text='#{d.DemoRowDynamicContent.lastName}' width='120'/>" +

                "</t:row>" +

                "<t:row>" +

                "<t:coldistance width='120'/>" +

                "<t:button text='OK' actionListener='#{d.DemoRowDynamicContent.onOK}'/>" +

                "</t:row>" +

                "</t:pane>"

            );

        }

    }

}

 

Have a detailed look onto the highlighted lines of code:

That's it. There is nothing more to take into consideration – you see: the dynamic content management basically is just the same as creating the XML definitions in the layout editor – but now by doing it by program.

You also see, that you can update the content of the dynamic content any time by just creating some new component-tree and passing it into the ROWDYNAMICCONTENTBinding instance.

Some remarks on passing XML versus passing ComponentNode-instances

Passing XML directly is the “hardcore” way of using this component – and maybe we only provide this function in order to better demonstrate that it is the same internal processing than the one that you know from static layout definitions.

Of course we 100% recommend to always use the ComponentNode-tree – and not to use the XML-way. Why?

Using “concrete Classes” for assembling ComponentNode Instances

In the example you saw that you can build up a tree structure, consisting of ComponentNode instances. Each instance holds its name (e.g. “t:label”), its attributes and its sub-instances.

Instead of using the generic ComponentNode class you may also use concrete classes: per component there is one class, e.g. there is a class “FIELDNode” for “t:field”. All attributes of the component are available through corresponding set-methods.

    PANENode pane = new PANENode();

    pane.setRowdistance("5");

    {

        ROWNode row = new ROWNode();

        pane.addSubNode(row);

        {

            LABELNode label = new LABELNode();

            row.addSubNode(label);

            label.setText("First Name");

            label.setWidth("120");

            FIELDNode field = new FIELDNode();

            row.addSubNode(field);

            field.setText("#{d.DemoRowDynamicContent.firstName}");

            field.setWidth("120");

        }

    }

 

The “concrete” classes are directly inheriting from class “ComponentNode” - so you can use the “concrete way” and the “generic way” in parallel.

Concatenated calling of set-methods

In order to reduce the code you have to write for dynamic scenarios you may concatenate the set-methods for a component node:

Instead of...

FIELDNode f = new FIELDNode();

f.setText(...);

f.setWidth(...);
f.setFlush(...);

 

You may also write:

FIELDNode f = new FIELDNode()

    .setText(...)

    .setWidth(...)

    .setFlush(...);

Restrictions...?

There are no restrictions of what you can assemble as content and then pass into the ROWDYNAMICCONTENTBinding instance. You may explicitly use:

Of course: the way you assemble the content must reflect a proper component hierarchy – e.g. in a PANE you first need a ROW in which you then can place a FIELD. We recommend to you, to first build up some simple version of what you want to dynamically create in a static way (JSP page) – before transferring it into a dynamic version.

Binding dynamically generated components to Java processing

Imagine you want to define a dialog in which you want to render a grid for each employee of a certain cost center – in the grid you may see for example the attendance time of the employee per day.

To do so you have to create some dynamic content in which for each employee a FIXGRID is created. For each grid you need an instance of FIXGRIDListBinding to manage the data of the grid.

By expressions - “traditional”

The core parts of your program would look like the following:

public class MyPageUI extends PageBean

{

    List<FIXGRIDListBinding> m_grids = new ArrayList<FIXGRIDListBinding>();

    

    public List<FIXGRIDListBinding> getGrids()

    {

        return m_grids;

    }

 

    …

    …

    private void renderContent()

    {

        m_grids.getItems.clear();

        …

        int counter = 0;

        for (Employee employee: m_employees)

        {

            FIXGRIDListBinding grid = new FIXGRIDListBinding();
           m_grids.add(grid);

            …

            FIXGRIDNode gridNode = new FIXGRIDNode()

                .setObjectbinding(“#{d.MyPageUI.grids[“+counter+”]}”)

                .setWidth(“100%”)
               …

                …;

            …

            counter++;

        }

    }

}

 

You see, it's like doing it in the static definition of a layout: the binding of the grid references to some list of grid, which is available via a “getGrids(...)” method.

Advantage:

Disadvantage:

Creating expressions: the pbx() method

PageBean implementations provide a method “pbx()” for supporting the generation of expressions within your code. “pbx” is a shortcut for “page bean expression”.

When e.g. building the following dynamic content...

public class XyzUI extends PageBean

{

        …

        FIELDNode f = new FIELDNode();

        f.setText(“#{d.XyzUI.someText”);

        Button b = new BUTTONNode();

        b.setActionListener(“#{d.XyzUI.onButtonAction}”);

        …

}

 

...then you always have to define the full expresion. With using pbx() these parts of the expression are already taken over automatically which are known by the page bean management on its own. So the code now looks like:

public class XyzUI extends PageBean

{

        …

        FIELDNode f = new FIELDNode();

        f.setText(pbx(“someText”));

        Button b = new BUTTONNode();

        b.setActionListener(pbx(“onButtonAction”));

        …

}

 

This reduces the probability of errors significantly – and the implementation does not have to be updated when e.g. changing the name of the page bean.

By direct binding - “more efficient”

To overcome the disadvantages you can use the “direct way” of binding:

public class MyPageUI extends PageBean

{

    List<FIXGRIDListBinding> m_grids = new ArrayList<FIXGRIDListBinding>();

    …

    …

    private void renderContent()

    {

        m_grids.getItems.clear();

        …

        for (Employee employee: m_employees)

        {

            FIXGRIDListBinding grid = new FIXGRIDListBinding();
           m_grids.add(grid);

            …

            FIXGRIDNode gridNode = new FIXGRIDNode()

                .bindObjectbinding(grid)

                .setWidth(“100%”)
               …

                …;

            …

        }

    }

}

 

You see: the grid instance is directly bound to the Grid-Node instance. There is no expression that needs to be built up and that 100% needs to match the property structure of the object. - It is also your decision if you require the “m_grids” member at all for some other processing.

Direct Binding of complex Object Instances

The direct binding can be applied to all attributes (except the “id” attribute...). In the concrete component node classes there is a corresponding “bind” method.

It can be directly used for all attributes that bind to some complex object on server side:

Direct Binding of simple Object Instances

For attributes pointing to some simple, “primitive” class (String, BigDecimal, …) there are special binding interfaces and default implementations:

public interface IValueDelegation<VALUECLASS extends Object>

{

    public Class getValueClass();

    public void setValue(VALUECLASS value);

    public VALUECLASS getValue();

}

public abstract class StringDelegation implements IValueDelegation<String>

{

    public Class getValueClass() { return String.class; }

}

public class StringValue extends StringDelegation

{

    String m_value;

    public StringValue() {}

    public StringValue(String value) { m_value = value; }

    public void setValue(String value) { m_value = value; }

    public String getValue() { return m_value; }

}

 

How does code look like when using these objects?

LABELNode l = new LABELNode()

    .setWidth(100)

    .bindText(new StringDelegation()

    {
       public void setValue() {} // not required for LABEL, values is never set
       public String getValue()

        {

            return <your_implementation>;

        }

    })

    .setForeground(“#FF8080”);

 

When the LABEL-TEXT attribute of the component is resolved then the corresponding “getValue()” is called transferring your value to the component.

In case of the LABEL-TEXT attribute the implementation of the setValue(...) method is not required, because a LABEL component will never change the text on client side. - In case of using the binding for e.g. a FIELD-TEXT attribute you need to implement both the set- and the get-method.

When to use “set...” and when to use “bind...” for simple Attributes

In the example above you see that we used “LABELNode.bindText(...)” but also used “LABELNode.setWidth(...)”.

It's the same decision that you would within a static layout definition: some attribute you directly set, other attributes – these ones that represent dynamic data - you bind via an expression.

ActionListener Binding

Last but not least: because the “actionListener” is not some value-attribute, but is some attribute pointing to some action-event-processing, there is also a specific binding object:

BUTTONNode b = new BUTTONNode()

    .setText(“OK”)

    .setWidth(“100+”)

    .bindActionListener(new IActionListenerDelegation()

    {

        public void onAction(ActionEvent event)

        {

            …

            …

        }

    });

ROWDYNAMICCONTENT Component <==> DYNAMICCONTENT Component

There is a counter part to the ROWDYNAMICCONTENT component: the DYNAMICCONTENT component.

The simple reason for having two components:

The counter part bean property for the DYNAMICCONTENT component is the class DYNAMICCONTENTBinding, offering the exact same interface as ROWDYNAMICCONTENTBinding (actually DYNAMICCONTENTBinding is an “empty extension” of ROWDYNAMICCONTENTBinding).

Details on managing Ids

You may have recognized that in the layout XML that was created dynamically in the example above there was no assignment of id-attributes. When defining a static layout then ids are mandatory attributes – though you will seldom recognized this, because they are generated internally.

The simple rule is: if you do not define ids on your own within your layout XML then the ROWDYNAMICCONTENT component will do this job for you. - But: if you define ids, then you need to pay attention at your own to not assigning the same id twice. Double assignment of ids will result in an ugly error “deep in the JSF functions”... Only assign ids, if you really have a good reason to.

What could be a good reason?

You need to be aware of that an id is the core parameter to indicate at client side that there is no change of the control structure. This means: the client, when executing its delta management when receiving an XML layout from the server, checks components that already were drawn against components coming through the new XML layout by their id. If the id matches, then the client component is continued to be used on client side, if the id does not match then the client component is removed and the new client component is drawn.

This means:

Style Management – Outsourcing style information

Use the Style Editor!

Before we start any explanation on the styling of CaptainCasa component, we strongly recommend to you...: use the tool Style Editor, which is part of the CaptainCasa toolset! It drastically simplifies working with the configuration files that are explained in the following sections.

Single component styles can inherit information from some parent component style. Whole Style files can inherit information from parent style files (you extend one style from another one). - All this means that you might have to combine information that resided in several files in order to know how a component actually is styled.

The style editor automatically combines all this information so that you really see what the current style of a certain component is. You clearly see which parameters are inherited, and which are the ones that you explicitly defined.



The Style Editor maintains all style configuration files that you will get to know in the next sections – so it is not some “second way” of doning the styleing, but it is exactly working on top of what is explained now.

Overview

CaptainCasa Enterprise Client comes with a style concept that provides the following functions:

Basics

Design time artifacts

Styles are kept within the directory “eclntjsfserver/styles”. Within your project the default styles coming with CaptainCasa are part of the “webcontentcc” directory. If creating own styles then you need to add the corresponding information to the “webcontent” part of your project.

There is one directory per style in which all information for the style is located:

<project>

  webcontent(cc)

    eclntjsfserver

      styles

        ...

        ccclassicrisc

        defaultrisc

        defaultdarkrisc

        ...

 

Inside each style directory there are two files by default:

<project>

  webcontent

    eclntjsfserver

      styles

        defaultrisc
         riscstyle.xml // name depends from style

          style.xml

 

Run time artifacts

At runtime there is a .css file and a .js-file that is created per style. Up to version 20190101 these files were explicitly generated as part of the development process. Now these files are dynamically generated out of the XML definitions at run time.

You may still access the definitions through the browser - e.g. the .css-file of a style can be accessed via...

http://<host>:<port>/<webapp>/eclntjsfserver/styles/<stylename>/riscstyle.css

 

...and the generated CSS file can be accessed via:

http://<host>:<port>/<webapp>/eclntjsfserver/styles/<stylename>/riscstyle.js

 

How things are bound... (1)

Within a component definition there is the attribute STYLESEQ (for “style sequence”). This attribute is the link into the CSS-style management.

Example:

...

<t:button ... styleseq=”riscbutton” .../>

...

 

The button now explicitly uses the style class “riscbutton” of the .css management. The “defaultrisc” style's riscstyle.xml-definition which looks like:

<class n="riscbutton">

    <risc n="font" v=""@fontFamily@" @fontDefaultSize@ @buttonFontWeight@"/>

    <risc n="background" v="@buttonBackground@"/>

    <risc n="border" v="1 1 1 1"/>

    <risc n="margin" v="@buttonMargin@"/>

    <risc n="inset" v="@buttonInsets@"/>

    <risc n="_backgroundModifierFocus" v="@backgroundModifierFocusDark@"/>

    <risc n="_animationAction" v="@backgroundAnimationAction@"/>

    <style n="font-family" v="@fontFamily@"/>

    <style n="font-size" v="@fontDefaultSize@px"/>

    <style n="background" v="@buttonBackground@"/>

    <style n="border" v="1px solid @buttonBorderColor@"/>

    <style n="border-radius" v="5px"/>

    <style n="cursor" v="pointer"/>

    <style n="white-space" v="nowrap"/>

    <style n="box-shadow" v="@buttonBoxShadow@"/>

    <class n=" > .riscelement">

        <style n="font-family" v="@fontFamily@"/>

        <style n="font-size" v="@fontDefaultSize@px"/>

        <style n="font-weight" v="@buttonFontWeight@"/>

        <style n="color" v="@buttonForeground@"/>

        <style n="cursor" v="pointer"/>

        <style n="white-space" v="nowrap"/>

    </class>

    <class n=".disabled">

        <style n="cursor" v="default"/>

        <style n="opacity" v="0.4"/>

    </class>

    <class n=".disabled > *">

        <style n="cursor" v="default"/>

    </class>

</class>

 

The XML definition itself is the base for some generated .css-file:

...

...

.riscbutton

{

    font-family: Trebuchet MS;font-size: 12px;

    background: #DDDDDD;

    border: 1px solid #a0a0a0;

    cursor: pointer;

    white-space: nowrap;

}

.riscbutton > *

{

    font-family: Trebuchet MS;

    font-size: 12px;

    font-weight: normal;

    color: #000000;

    cursor: pointer;

}

.riscbutton.disabled

{

    cursor: default; opacity: 0.4;

}

.riscbutton.disabled > *

{

    cursor: default;

}

...

...

 

So the attribute STYLESEQ is a quite important one! It is the link from the component into the CSS-style.

How things are bound... (2)

You may directly assign the STYLESEQ attribute to the component within your layout definition (.jsp/.xml) – or you may indirectly set it by using a CaptainCasa “style.xml” definition.

The “style.xml” is a quite simple file – and contains some configuration about how CaptainCasa presets certain component attributes. Inside the “style.xml” file definitions are done in the following way:

<style>

    ...

    <tag name="button" variant="default">

        <set attribute="styleseq" value="riscbutton"/>

    </tag>

    ...

</style>

 

So every time a BUTTON is created, then automatically the STYLESEQ attribute is preconfigured with value “riscbutton”. You may do this with any other attribute of BUTTON as well.

You may define certain style variants in the style.xml file:

<style>

    ...

    <tag name="button" variant="default">

        <set attribute="styleseq" value="riscbutton"/>

    </tag>

    <tag name="button" variant="popupfooter">

        <set attribute="styleseq" value="riscbutton"/>

        <set attribute="width" value="150"/>

    </tag>

    ...

</style>

 

The selection of the style variant is done within the layout definition using attribute STYLEVARIANT. By default the style variant “default” is applied to a control.

...

<t:button ... stylevariant=”popupfooter” .../>

...

 

As result now the button is preconfigured with STYLESEQ “riscbutton” and with WIDTH “150”.

Within the layout editor the attribute STYLEVARIANT can be set either just as a normal attribute or by some explicit selection:



The attributes that are preconfigured via the style.xml definitions of course can be overridden within your layout definition at any time. Example:

...

<t:button ... stylevariant=”popupfooter” width=”200” .../>

...

 

In this case the WIDTH definition within the layout definition (.jsp/.xml) is “stronger” than the WIDTH definition in the style (style.xml).

Two Levels of Style Management

As you can see there are two levels of style management that are applied:

So the base look and feel of the component is coming from the CSS-level. The definition of the CSS level may be overridden by definitions on the Attribute-based style definition (style.xml). And these may finally be overridden by explicit attribute-definitions within the component.

Where the look and feel should be defined...

Let's take as example the background color and border of a button which you want to centrally define for all your “Save”-buttons. Inside your dialog definition you want to define...

...

<t:button text=”Save” stylevariant=”savebutton” .../>

...

 

...so that the concrete definition of how a “Save”-button looks like is kept outside the individual component definition.

There are now – theoretically – two ways to go:

...

<tag name=”t:button” variant=”savebutton”

                     extendstag=”t:button” extendsvariant=”default”>

    <set attribute=”background” value=”#C0C0C0”/>

    <set attribute=”border”

         value=”color:#FF0000;left:2;top:2;right:2;bottom:2”/>

</tag>

...

 

riscstyle.css:

 

...

.savebutton

{

    background-color: #C0C0C0;

    background-border: 2px solid #FF0000;

}

...

 

style.xml:

...

<tag name=”t:button” variant=”savebutton”

                     extendstag=”t:button” extendsvariant=”default”>

    <set attribute=”styleseq” value=”savebutton”/>

</tag>

...

 

Which one is the better way?

We definitely recommend to push all look and feel related issues into the CSS styling. This is not only the common way of dealing with HTML – this is also the one which is most efficient from runtime point of view.

Selecting the Style

...by Configuration

You may define a default style for all users:

Open the “Session Defaults” configuration of your project within the layout editor and define the XML file as follows:

<sessiondefaults

    ...

    style="defaultgreenrisc"

    ...

/>

 

The XML is stored in “<project>/webcontent/eclntjsfserver/config/sessiondefaults.xml”.

...by URL Parameter

In addition you can select the style by adding URL parameter “ccstyle=<styleName>” to the .ccapplet or .ccwebstart-URL for starting a CaptainCasa Enterprise Client page.

http://localhost:50000/demos/workplace.workplaceRisc.risc?ccstyle=defaultbluerisc

 

The same can be done at any place where you reference the .jsp/.xml page via URL.

Defining some own style

Creating the style

In the CaptainCasa toolset there is a menu item “Create RISC style...”:

Select this menu item. A dialog will appear in which you simply have to define the name of your style and then presse the “Create Style” button. In the following text we will assume the name of your new style is defined as “ownstyle”.

 

The following files will be created within your project:

<project>

  webcontent

    eclntjsfserver

      styles

        ownstyle

          riscstyle_ownstyle.xml

          style.xml

 

After having creared the style you may start the style editor by selecting “Edit existing style” from the menu.

Editing the style in the style editor

Within the style editor you can now set up your own style. The first 3 tabs of the tool are the ones that generate the “riscstyle_<styleName>.xml” definition, which itself is the base for the “.css” generation at runtime.

The principles behind are:

Result: if it's just some basic color changes you want to apply, then take a look onto the “Style”-tab, change the variables there and that's it. - If you really want to update the rendering on component level, then you need to dive into the corresponding style classes and the detail variables referenced there.

The component-based style definition (style.xml) is done within the “Control variants”-tab. Here you can edit/set up the style variants and their attribute values.

Details on the .xml based CSS definition

Nested style class definitions

Components are typically assembled out of several inner areas – each of them referencing some own style class definition. This is the reason why a style class definition typically contains inner class definitions.

Definitions per style class

There are three types of definitions within a style class definition:

RISC parameters

Style classes that are referenced by RISC components need to define these parameters which are relevant for sizing the control in a proper way.

The parameters that need to be known to RISC are:

Example: <risc n=”border” v=”1 1 1 1”/>

Default: <risc n=”border” v=”0 0 0 0”/>

 

Example: <risc n=”inset” v=”4 10 4 10”/>

Default: <risc n=”inset” v=”0 0 0 0”/>

 

Example: <risc n=”border” v=”1 1 1 1”/>

Default: <risc n=”border” v=”0 0 0 0”/>

 

Example: <risc n=”font” v=”"Arial" 12”/>

         <risc n=”font” v=”"Arial" 12 bold”/>

Exapmple: <risc n=”background” v=”linear-gradient(to bottom, #FF0000, #00FF00)”/>

 

In addition there might by additional parameters, starting with “_” that contain content that is specific to a certain component: a component implementation can outsource configuration data that it internally requires into risc-parameters so that they can be set as part of the style definition.

Details on Style Definition Files (Component-Attribute Level, style.xml)

As explained in the previous chapter, style definition files are the ones to pre-configure components on server side. Per component there is a default variant “default” and you may add any number of other variants.

Defining Styles that extend other Styles

You may define a style definition file completely on your own, in which you list all the components with all their variants. Or you may define a style definition file that extends an other style definition file. As consequence you take over all the information of the other file and only need to define the differences that you want to apply.

The format of the definition file is an extension of what you already know:

<style extends="defaultrisc">

 

    ...

    ...

 

    <tag name="label" extendsparenttag="true">

        <set attribute="font" value="weight:bold"/>

    </tag>

    <tag name="button">

        <set attribute="contentareafilled" value="false"/>

        <set attribute="font" value="weight:bold;size:14"/>

    </tag>

 

    ...

    ...

 

</style>

 

By specifying “extends” on style-level you can define the parent style of your style definition. As consequence all tag definitions of the parent style are implicitly taken over.

You now define your own tag definitions just as normal. By default all your tag-attribute definitions are, if defined, overriding the complete tag definition from the parent style. But you can also define “extendsparenttag” to be “true”: in this case all the definition within the corresponding tag of the parent style are mixed into what you define within your style.

The example above extends the default style in the following way:

Defining Tag Variants referring to other Tag Definitions

In a similar way you can define tag variants that take over the style attributes of other tag definitions, and themselves just add some extra definitions:

<style ...>

    

    ...

    ...

 

    <tag name="button">

        <set attribute="font" value="weight:bold;size:14"/>

    </tag>

    <tag name="button" variant="HIGHLIGHTED"

         extendstag="button" extendsvariant="default">

        <set attribute="contentareafilled" value="false"/>

        <set attribute="bgpaint" value="bgbackground(#FF000030)"/>

    </tag>

 

    ...

    ...

 

</style>

 

The “HIGHLIGHTED” variant of the example extends the normal button definition. (Remember: if not explicitly defining a variant then the variant “default” is assumed – so the first button definition is the “default” one).

Of course you can mix both extension variants: the style extension described in the previous chapter and the tag extension that you see here. When mixing, the tag extension will be executed first, the style extension is applied afterwards.

Expressions as Style Attribute Values

In principal you may also use expressions as attribute value definition. Example:

<style ...>

    

    ...

    ...

 

    <tag name="pane">

        <set attribute="background" value="#{xxx.yyy}"/>

    </tag>

    ...

    ...

 

</style>

 

Please note: like with the normal style management style values are applied when the page components are rendered on server side the first time. This means: if the value behind the expression changes, then this is only reflected by components which are new – not for existing ones.

Also use “HttpSessionAccess.reloadClient()” in order to re-create all components and as consequence to re-process all expressions.

Style Manager API

Please check the Java API documentation (Java Doc) within the style area. The central class “StyleManager” provides functions for accessing style information at runtime.

Refreshing the Browser when updating Styles

When working with styles then the typical procedure is: change the style... - ...and check how it looks like. So the cycle between changing the style and testing it should be as short as possible.

By appending “&ccresetbuffers=true” to the “.risc”-URL all the server side style data (style.xml level) is refreshed. So start the page by appending this parameter.

In the browser you have to make sure that the style data is not buffered, when testing a certain page.

Adding own fonts

You may want to add own fonts to your application. In this case please proceed as follows:

<webcontent>

  eclntjsfserver

    styles

      yourstyle

        ...

        fontreference.css      <== file to be added

 

@font-face

{

    font-family: 'YourFontName';

    src: url('../../../eclnt/risc/fonts/...yourFontDefinition...');

}

 

Referring to images

In principal it's simple... - but also might be confusing at first time: the referencing of images. Within the style management there are three areas where to reference images – each of the areas being explained in the following text.

Of course the way to go is: always use relative image definitions! Never expect your web application to be deployed with a certain name!

“style” values in the style definition

A css style is always generated in the following way:

“ecltnjsfserver/styles/<styleName>/riscstyle.css”

So this is the reference for all direct style definitions. Example:

<class n="riscshiftcontainerwithnavigation">

    ...

    <class n="_left">

        <style n="background" v="url(../../../eclntjsfserver/images/iconssvg/shiftcontainer_left_16x16.svg) no-repeat center"/>

 

“risc” values in the style definition

The “risc” style values are directly used by the JavaScript processing on the client side and are transferred into image definitions without any conversion.

A page is always loaded as “.risc” page. Example: there is a page:

http://localhost:50000/demos/workplace.demohelloworld.risc

The context of the “.risc” page is the web application – in the example this is “http://localhost:50000/demos/”: So all relative image addresses that are directly managed within the page are referring to this context.

    <class n="riscshiftcontainerwithnavigation">

        ...

        <class n="_left">

             ...

            <risc n="background" v="url(eclntjsfserver/images/iconssvg/shiftcontainer_left_16x16.svg) no-repeat center"/>

            ...

 

Image definitions in the Control Variants (style.xml)

While the “style” and “risc” value definitions are direct part of the client processing, the control variants are part of the server processing – they are just some presetting of attributes of component instances.

Consequence: here the normal rules of setting the image in “.jsp/.xml” files are applied: all images should start with “/” - and the “/” is referring to the root of the web application. Example:

<tag name="helpicon" variant="default" extendstag="icon" extendsvariant="default">

    <set attribute="image" value="/eclntjsfserver/images/lightbulb.png"/>

</tag>

 

Defining and Accessing Style Values

Defining central “Style Values”

As part of a style definition there are two places in which you can defined variables:

Referencing from Page Definition

Both definitions can be used directly in a page by using the Expression “#{ccstylevalue.<nameOfValue>}”:

Examples:

<t:button … image=”#{ccstylevalue.ccDeleteImage}” … />
or

<t:button … image=”#{ccstylevalue['ccDeleteImage']}” … />

<t:pane … background=”#{ccstylevalue.@buttonBackground@}” … />

or

<t:pane … background=”#{ccstylevalue['@buttonBackground@']}” … />

 

Referencing from Code

User method “StyleManager.getStyleValue(<nameOfValue>)” to retrieve the style value from your Java code – using the currently selected style of the session:

String image = CCStyleManager.getStyleValue(“ccDeleteImage”);

String background = CCStyleManaer.getStyleValue(“@buttonBackground@”);

 

Combining style information from different contributors

Example

CaptainCasa comes with a set of default styles:

A user of CaptainCasa takes one of these styles as base-style and then inherits an own style definition from this one. Example:

Now imagine that you write a library of components (Page Bean Components). The library concept allows to you that you package all issues of the components together in one .jar file, including: the layout definition (.jsp/.xml), the code (.class), resources (.jpg, …).

Typically, when writing own components, you also create own style classes and/or own style variants. You could now create an own style variant “comp1” e.g. inheriting from “default202201risc” wher you add your definitions.

But...: now you have your style “comp1” for the page bean components, and the users who wants to user you pae bean components has style “usage1” - and would need to explicitly copy all yout “comp1” definitions into “usage1” to make the components work.

This is of course no solution, especially when it comes to regular updates that are done to libraries.

Enriching an exisiting style

So, in addition to the “extension” of styles, there is the concept of “enrichting” style. The concept is rather simple:

When CaptainCasa reads a style (e.g. “defaultrisc”) then there are two definition files that are read:

While reading the style files, CaptainCasa checks in the class loader for any occurance of the resources in the package “eclntjsfserver/stylextensions”. Example: when reading style “defaultrisc” then the following resources are checked:

/eclntjsfserver

  /styleextensions

    /defaultrisc

      style.xml

      riscstyle.xml

 

And when reading “default202006risc” then the following resources are checked:

/eclntjsfserver

  /styleextensions

    /default202006risc

      style.xml

      riscstyle_default202006.xml

 

This means: any .jar file that you add as runtime library to your application may bring own addons and contribute them into the corresponding existing styles. They just need to be places into the .jar file into the correponding location.

CaptainCasa will, when reading the styles, combine the “fully style” that is available out of the style itself and all its contributions that come with various “.jar” files. Basically, CaptainCasa does nothin else than concatenating the XML definitions to from one big XML before parsing and interpreting this XML.

Conclusion 1

The style is dynamically assembled out of its basic definition and all its contributions.

If you add a library of Page Bean Components and bring in new style classes and style variants, then embed the correpsonding definitions into your .jar file – best using “defaultrisc” as base to add your definitions.

As consequence all library users will take benefit out of your style definitions without manually copying them from one style to the next.

Conclusion 2

You may also use this “enriching” feature if you have loosely coupled teams working on different part of the project. Each team can add own style definitions in its own “.jar” libraries. (Of course you have do define naming conventions so that class and variant names do not overlap.)

There is no need to have one central style only, that typically serves as some kind of bottle-neck within a project.

 

Style Management – Styling Issues

The previous chapter concentrated on showing how to define styles so that style definitions are outsourced into corresponding style definitions. This chapter is a collection of issues that have to do with the way how you actually style your application.

Image management

You may pass images in various formats:

Because scaling typically is an important aspect of your application – especially when it is used on difference types of devices – we strongly recommend to use SVG-images in front of pixel formats.

There are also special components in CaptainCasa to directly support font images (awesome font, SAP image font) which support scaling as well. Please check the corresponding components (FONTICON, AWESOMEFONTICONS) for more information on these.

Image sizing as part of the name

You can accelerate CaptainCasa's frontend processing in the area of images significantly if the size of an image can be directly derived from its name.

The default naming patterns are:

 

/[<DIR>.]<name>_<WIDTH>x<HEIGHT>.<EXTENSION>

/[<DIR>.]<name>.<WIDTH>x<HEIGHT>.<EXTENSION>

 

 

Examples:

 

/images/icons/warning_32x32.png

/images/icons/warning_16x16.png

/images/icons/error_16x16.svg

 

 

You can bring in own patterns as well – by implementing a JavaScript extension in the client. Check the “Developers' Guide – RISC Addons” for more information on this.

Providing images in the webcontent - or in some Java package

Images “typically” are stored in the webcontent-part of an application.

Please note that it is also possible (and 100% valid) to access images through the server side class loader. The URL format is:

 

/[<PACKAGE>.]<NAME>.<EXTENSION>.ccclresource

 

 

Examples:

 

/org.eclnt.xyz.resources.warning_32x32.png.ccclresource

/org.eclnt.xyz.resources.error_16x16.svg.ccclresource

/org.eclnt.xyz.resources.questionmark.svg.ccclresource

 

 

This is an important feature when e.g. using images in Page Bean Components: these components are delivered as .jar file, in which all resources need to be accessed via calss loader.

Defining image names with reference to style variables

Especially when using flat images, then the color of the image heavily depends on the styling of the background area. A dark image icon is only looking nice if the background is styled in a light way. - So there is a high level of dependency between the style and the selection of image colors.

This is one of the reasons why there is the possibility to directly reference style variables within an image name:

 

<style>

    ...

    ...

    <var n=”@imageDir@” v=”light”/>

    ...

    ...

</style>

 

 

 

<t:icon … image=”/images/@imageDir@/xyz.png” … />

 

 

Consequence: in the one style “@imageDir@” might be defined in a different way than in another style.

SVG Image Management

We like SVG images...! ;-)

Dynamically sizing and coloring flat SVG icons

A flat SVG icons consists of a definition as follows:

 

<svg xmlns="http://www.w3.org/2000/svg"

     viewBox="0 0 448 512">

    <path d=" … … … " />

</svg>

 

 

Updating this image to have a defined size and to have a defined color means that only some simple definitions have to be added:

 

<svg xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 448 512"

     width="16"
    height="16"

     fill="#F0F0F0" >

    <path d=" … … … " />

</svg>

 

 

CaptainCasa provides some server side processing which does this replacement by passing the corresponding information through the URL of the image. The format is:


/[<DIR>.]<NAME>.<COLOR>.<WIDTH>x<HEIGHT>.ccsvg

 

or

 

/[<PACKAGE>.]<NAME>.<COLOR>.<WIDTH>x<HEIGHT>.ccsvg

 

The server side processing first checks for the SVG file in the webcontent – if it does not find it there then it checks within the class loader.

Example

In your webcontent there is a flat SVG image:

 

<project>

    webcontent

        images

            iconssvg

                warning.svg

 

 

This SVG image may now be accessed in the following ways:


/images.iconssvg.warning.#606060.64x64.ccsvg

/images.iconssvg.warning.#800000.16x16.ccsvg

 

As said the image could also reside in a Java package and then gets loaded via server side class loader. In this case the image would reside in package “ images.iconssvg”.

Deriving the color (and/or size) from style

Referencing style variables is of course also possible together with the dynamic SVG access.

Example: in the style you define the following variables:

<style>

    …

    <var n=”@imageColorLight@” v=”#F0F0F0”/>

    <var n=”@imageSizeNormal@” v=”16x16”/>

    …

</style>

 

You then can define the URL in the following way:

 

/images.iconssvg.warning.@imageColorLight@.@imageSizeNormal@.ccsvg

 

 

Defining independent style classes

When using the style editor then you typically create or update style classes for certain components. These style classes contain both variables, “RISC values” and CSS style definitions.

Each style class has a name that by default is prepended with a “.” when being converted into a style class of the generated CSS. Example:

Style class: “riscbutton”

 

Occurance of class in generated CSS:

 

.riscbutton

{

    ...

    ...

}

 

Sometimes you want to define style classes which are pure CSS classes (i.e. no variable definitions, no “RISC values”) - and for theses ones you do not want that a “.” is automatically prepended.

In these case use the class “riscinternal_directcss” to extend from. Example:

<class n="::-webkit-scrollbar" extends=”riscinternal_directcss”>

    <style n="width" v="16px"/>

</class>

 

Generated CSS:

 

::-webkit-scrollbar

{

    width:16px;

}

 

Viewing the generated CSS

You can simply view the generated CSS by opening a URL that follows the pattern:

http://<host>:<port>/<webapp>/eclntjsfserver/styles/<style>/riscstyle.css

 

Maybe you are also interested in seein the “RISC values” that are sent as JSON content. In this case you open:

http://<host>:<port>/<webapp>/eclntjsfserver/styles/<style>/riscstyle.json

 

Internationalization Issues

The Enterprise Client allows to define user interfaces which are “fully internationalized”. This includes:

Client Locale <==> Server Locale Settings

Before getting into details first have a look onto which pieces of software are concerned when talking about internationalization:

So, by default, both programs are decoupled from internationalization point of view. This makes sense in some scenarios and may be a bit confusing in other scenarios. Basically we took over this concept from normal browsers: also your normal HTML browser has a certain language (typically defined with installation) that has nothing to do with the language of the HTML content that is shown inside: you may start an English browser and view German pages. Print & configuration dialogs & default dialogs (e.g. calendar) will come up in the language of the browser, content dialogs of the application will come up in the language of the server side application.

This is important to always keep in mind. - In the next chapters we will first introduce how to configure the client locale settings, then we will talk about the server side settings – and then talk about strategies in order to combine both.

Client Side Internationalization

Language and Country Settings

The country settings of the client define the way certain data is formatted. This includes:

The language settings define the texts that are shown in some components, e.g. the calendar component or the file download component.

Both definitions are completely independent from one another. It is possible to apply any combination of country and language settings.

Automatic selection of Client Language and Country Settings

When the JavaScript client starts up then it asks the browser for its language/country settings using a JavaScript API. The result is checked against the available client languages. If there is no match then a German localization is used by default.

Update the Client Settings from Server Side

There is a component CLIENTCONFIG that allows to update the client's language and country definition from server side at runtime. The CLIENTCONFIG component provides a couple of client attributes that are normally passed statically when starting the client – and allows to set these parameters at runtime by your server side processing.

For updating the client side localization there are two attributes CLIENTCONFIG-LANGUAGE and CLIENTCONFIG-COUNTRY.

Please note: the CLIENTCONFIG component should be placed at the beginning of the out-most page of your scenario. This means: when building up pages out of other pages using ROWINCLUDE or ROWPAGEBEANINCLUDE then the CLIENTCONFIG typically should be defined within the “outest page”.

Where the client looks for languages/countries

The client loads its configuration from the files that are available in the “<webcontent>/eclnt/risc/i18n” directory. Within this directory the client searches for files following the file name pattern “country_*.xml” and “language_*.xml”:

For each country and for each language that is supported, there is a corresponding XML file definition. For countries there are also generic country definitions (CCTYPE*) that you can use in the same way you use normal country settings.

Adding own language/country definitions

The default language/country definitions are coming with XML files which are part of the “webcontentcc” part of your project.

You may add own definitions within your “webcontent” part, so that your own definitions and the default definitions are mixed during deployment. Example: in case you want to add some country “XX” and some language “yy”, then your project (if following the default project structure) looks like:

<project>
/src
/webcontent
   /eclnt
     /risc

        /i18n

          country_XX.xml
         langauge_yy.xml

      ...

      ...

  /webcontentcc

    /eclnt
     /risc

        /i18n

          ...

          country_DE.xml
         country_FR.xml
          ...
         langauge_de.xml

          langauge_fr.xml

          …

 

Use the files “country_DE.xml” and “language_en.xml” file from the default webcontentcc directory as copy source when creating your own files.

Server-side (Application-side) Internationalization

Setting Server Side Locale

You set the language definition in the following way:

    public void onGerman(ActionEvent ae)

    {

        HttpSessionAccess.setCurrentLocale(Locale.GERMANY);

    }

    

    public void onEnglish(ActionEvent ae)

    {

        HttpSessionAccess.setCurrentLocale(Locale.ENGLISH);

    }

 

Typically you set the Locale at the very beginning of your user's session, e.g. at logon point of time. You may use any Locale definition that is available in Java.

Different users may be logged on with different languages.

Accessing Literal Translations - Basics

Within your server side application you may use various ways to support multiple languages for your literals.

For all the ways you need to define an expression for accessing the literal instead of hard-wiring the literal. Example: instead of defining the attribute LABEL-TEXT in the way “First Name”, you need to define an expression like “#{xyz.firstName}”.

In general we recommend to use option (1): in this case the management of resources is directly integrated into the tool environment (Layout Editor) and information is kept in a standard way: as resource bundle.

Option (2) is the one to do everything on your own, e.g. in case you keep your translation information in a text database.

The default way: using built in Resource Management

CaptainCasa Enterprise Client provides a default way of managing multi language resources: literal information is outsourced in form of resource bundles. Resource bundles are text files that keep the information in the following way:

firstName=First Name

lastName=Last Name

 

Resource bundles are kept in several files, each one containing the “property=value” pairs for a certain language and/or country.

Example: there may be the files:

literals.properties

literals_en.properties

literals_de.properties

 

The file name is built out of the “resource bundle”, the language (optionally: the country) and the extension “.properties”. The files are located in a package of your compiled program. During runtime the multi language management will select the right file, which is closest to the session's internationalization settings.

Property files have some strange management of non-ASCII characters. All characters above ASCII code 127 need to be encoded. The German translation of the literal “street” is “Straße” - in the resource bundle file it is written in the following way:

street=Stra\u00DFe

 

Now, this is the technology behind, you will see that the default way of managing resources is quite simple:

Within the project's web application, inside the directory /eclntjsfserver/config there is the file “resources.xml”. Either edit the file from the file system or use the project configuration:

In the file you specify the resource bundles that you want to maintain within your project. Typically you have exactly one resource bundle for literals:

<resources>

  <resource name="literals" package="workplace.resources"/>

</resources>

 

For each resource bundle you define the “base name” and you define the package, in which it is stored.

From now on you can use the “Resource Editor” tool, which is part of the Layout Editor:

In the tool you see the properties of the resource bundle as a list. You may update existing properties or create new ones. Each property is associated with an expression, that can be used within component definitions. The expression is formed in the following way: “#{rr.resourceBundleName.propertyName}”.

The tool maintains the default property file. E.g. if the resource name is “literals”, then the tool will maintain the “literals.properties” file. This should be the one that you constantly use during development, and that you later on use as base for translations into other languages.

Translations are quite easily done: just copy the base file (e.g. “literals.properties”) into another file (e.g. “literals.properties_de”) and then replace the property values. Use an editor which automatically translates non-ASCII characters into corresponding escape sequences, there are e.g. a plenty available as plugins for the Eclipse environment.

If at runtime a certain property name is resolved and no information is found within the corresponding resource bundle then a default value is returned. The default for this default value is a string that contains the name of the resource bundle and the name of the property that is resolved – so that you can directly see which value is missing in which resource bundle.

You can override this default behavior by assigning an explicit return value within the resource definition:

<resources>

  <resource name="literals" package="workplace.resources"

            defaultvalue=”null”/>

</resources>

 

You man define either “null” or any other text value (e.g. “Literal missing”).

 

When using the resource management as described, then you can access literals within your server side Java code in the following way:

ResourceManagement.findLiteral(“literals”,”firstName”);

 

...accesses the same literals as accessed via:

#{rr.literals.firstName}

Doing it on your own...!

This chapter shows how you may on your own use managed beans to access multi language information. You should read this chapter in case you want to implement an own resource management. This chapter again uses resource bundles to keep the literal translations, but the main focus is on showing how to arrange managed bean codings in order to access the literal translations.

First, there's the definition of resource files. Within the resource files there is the translation from a key into a string value. Resource files have a “base name” and have variation names depending on the language information they are defined for. (All this is default Java resource management, so please also have a look into the JavaDoc of the class “ResourceBundle”).

Example: there are two files, which are part of the classes / library-jar file:

This is the content of the file “Literals.properties” within the package “org.eclnt.jsfserver.i18n.resources”:

 

OKPopup_ok = OK

 

YESNOPopup_yes = Yes

YESNOPopup_no = No

 

 

And this is the content of “Literals_de.properties” within the same package:

 

OKPopup_ok = OK

 

YESNOPopup_yes = Ja

YESNOPopup_no = Nein

 

You see: two files with two different translations for the same keys.

The next step is to provide a managed bean that accesses the resource bundle. The managed bean claims to be a map, so that an expression is translated into a corresponding “get()” call against the map:

public class I18N

    implements Map<String,String>, Serializable

{

    protected static String s_bundle = "org.eclnt.jsfserver.i18n.resources.Literals";

    

    public void clear() {}

    public boolean containsValue(Object value) { return false; }

    public Set<Entry<String, String>> entrySet() { return null; }

    public boolean isEmpty() { return false; }

    public Set<String> keySet() { return null; }

    public String put(String key, String value) { return null; }

    public void putAll(Map<? extends String, ? extends String> m) { }

    public String remove(Object key) { return null; }

    public int size() { return 0; }

    public Collection<String> values() { return null; }

    

    public boolean containsKey(Object key)

    {

        String result = get(key);

        if (result == null)

            return false;

        else

            return true;

    }

    

    public String get(Object key)

    {

        ResourceBundle rb = ResourceBundle.getBundle(s_bundle,FacesContext.getCurrentInstance().getViewRoot().getLocale());

        return rb.getString(key.toString());

    }

    

}

 

Only two methods are implemented of the map: the get() method and the containsKey() method. You see that the get() method is delegating the call to the resource bundle. The locale used for accessing the resource bundle is taken from the root node element (getViewRoot()) - this is “normal JSF”.

All the manipulation part of the map is not required, because the map serves only the purpose of accessing literals.

The last step is to declare the class above as managed bean under a certain name. This is done in “faces-config.xml”:

<faces-config>

    ...

    ...

    <managed-bean>

        <managed-bean-name>eclnti18n</managed-bean-name>

        <managed-bean-class>org.eclnt.jsfserver.i18n.I18N</managed-bean-class>

        <managed-bean-scope>request</managed-bean-scope>

    </managed-bean>

    ...

</faces-config>

 

That's it: you now can refer to a literal within a JSP definition in the following way:

<f:view>

<h:form>

<f:subview id="eclntjsfserver_popups_yesnog_8">

<t:rowbodypane id="g_2" >

  <t:row id="g_3" >

    <t:textpane id="g_4" height="100%" text="#{eclntdefscr.yesNoPopup.text}" width="100%" />

  </t:row>

  <t:rowdistance id="g_5" height="10" />

  <t:row id="g_6" >

    <t:coldistance id="g_7" width="50%" />

    <t:button id="g_8" actionListener="#{eclntdefscr.yesNoPopup.onYes}" image="../images/yesnopopup_yes.png" text="#{eclnti18n.YESNOPopup_yes}" />

    <t:coldistance id="g_9" />

    <t:button id="g_10" actionListener="#{eclntdefscr.yesNoPopup.onNo}" image="../images/yesnopopup_no.png" text="#{eclnti18n.YESNOPopup_no}" />

    <t:coldistance id="g_11" width="50%" />

  </t:row>

</t:rowbodypane>

</f:subview>

</h:form>

</f:view>

 

Please note when transferring this example to your application:

Maybe you want to add a method to also access the resource bundle from outside, so that you can access your literals from application processing, then you may add a method like:

    /**

     * This method may only be called within a request processing.

     */

    public static ResourceBundle getBundle()

    {

        return ResourceBundle.

               getBundle(s_bundle,

                         FacesContext.getCurrentInstance().getViewRoot().getLocale());

    }

 

Now you can also add your literals in order to for example output dynamic messages.

One last comment: pay attention that property resource files are NOT UTF-8 based. Unfortunately. You need to escape all characters with an internal integer value > 127. Best, you download a property file editor into your IDE for maintaining property files. There are plenty available for the Eclipse toolset, e.g. via http://propedit.sourceforge.jp/index_en.html.

Synchronizing Client Side Locale and Server Side Locale Settings

As introduced at the beginning of the chapter client locale settings and server locale settings in principal are independent from one another. But, there are ways to combine both.

Scenario: Client Settings dominate Server Settings

The current country/language settings are passed to the server side via http-header parameters (“eclnt-language”, “eclnt-country”).

Based on this, there is a special configuration on server side that will update (if required) the server side settings. Edit the configuration file “webcontent/eclntjsfserver/config/sessiondefaults.xml” so that the attribute “takeoverclientlocalesettings” is set to “true”:

<sessiondefaults

    ...

    takeoverclientlocalesettings="true"

    ...

/>

 

Scenario: Server Settings dominate Client Settings

Use the component CLIENTCONFIG, attributes LANGUAGE and COUTNRY in order to control the client locale settings from server side. Typically the CLIENTCONFIG component is placed into the out-most page of your application so that it is contained only one time – in order to avoid confusion.

Management of Dates (and Times)

When working with the class “jva.util.Date” in Java then you always need to pay attention to correctly managing the time zone aspect. Please read the Javadoc for details if being unsure about dealing with the classes “Date”, “Calendar”, “TimeZone”.

Every time a date is managed in a component (e.g. Calendar) then you need to also add a time zone reference. The date value that is passed “via the line” between client and server is the long-value of the date. A reliable date interpretation is only possible when ensuring that the time zone that is used in the client is exactly the time zone used on server side.

Online Help (“F1 Help”)

CaptainCasa contains a simple but flexible online help management. It provides the following functions:

In addition to the default online help management there is an interface to plug in any other online help management frameworks. As consequence you can store the online help information in a completely different way than the default, and you can visualize the online help to the user in a different way.

Assignment of HELPIDs

The base of all is the assignment of so called “helpid” values to components. Most input components offer an HELPID attribute, into which you can pass a corresponding value:

    <t:field id="g_6" helpid="firstName" width="200" .../>

 

The value that you assign is completely up to you. When using the default online help framework then the value must only contain characters that you can use for building proper file names.

Components with defined HELPID attribute automatically support the following behaviour:

The Default Framework

By default all online help texts are HTML texts that are stored within a certain directory. The name of the directory is specified in the configuration file “eclntjsfserver/config/onlinehelp.xml”:

<onlinehelp contentdirectory="/onlinehelp/">

</onlinehelp>

 

Within the directory that you define the following file structure needs to be defined:

webcontent/

  onlinehelp/

    de/

      firstName.html

      ...

    en/

      firstName.html

      ...

 

You see:

Plugging in an own Framework

Interface IOnlineHelpProcessor

This is the central interface that is called on server side when the user presses F1 on a component:

package org.eclnt.jsfserver.onlinehelp;

 

public interface IOnlineHelpProcessor

{

    public void processOnlineHelp(String helpId, String language);

}

 

The help-id that fits to the component and the language are passed as parameters – now it's the processor's task to present to the user an adequate online help.

The name of the class that is implementing the interface needs to be defined in the configuration file /eclntjsfserver/config/onlinehelp.xml:

<onlinehelp

    contentdirectory=”/onlinehelp/”

    onlinehelpprocessor=

        "org.eclnt.jsfserver.onlinehelp.defaultimpl.OnlineHelpProcessorJBrowser">

</onlinehelp>

 

If not explicitly defined, then by default the implementation “OnlineHelpProcessorJBrowser” is selected.

Default Implementation “OnlineHelpProcessorJBrowser”

The default implementation brings up a URL that is composed out of the directory for the online help (“contentdirectory”), the language and the help-id.

Example: if the “contentarea” is defined as “/onlinehelp/”, and if the language is “en” and if the help-id is “firstName”, then the URL would be: “/onlinehelp/en/firstName.html”.

On client side a JBROWSER component is popped up in a modeless dialog that shows the URL directly aside the component that requested the online help:



Because things are quite simple, have a look into the implementation:

package org.eclnt.jsfserver.onlinehelp.defaultimpl;

 

...

...

 

/**

* Online help processor that opens up a modal dialog in which a JBrowser

* Component is loaded with a URL of the online help page.

*/

public class OnlineHelpProcessorJBrowser implements IOnlineHelpProcessor

{

    ModelessPopup m_popup;

    

    public void processOnlineHelp(String helpId, String language)

    {

        // calculate URL

        String url = OnlineHelpConfiguration.getContentDirectory();

        if (url == null)

        {

            throw new Error("Online help is not configured yet: /eclntjsfserver/onlinehelp.xml, content directory not defined");

        }

        if (!url.startsWith("/")) url = "/" + url;

        if (!url.endsWith("/")) url = url + "/";

        url = url + language + "/" + helpId + ".html";

        OnlineHelp.setOnlineHelpJBrowser(url);

        m_popup = ModelessPopup.createInstance();

        IModelessPopupListener mpl = new ModelessPopup.

                                         IModelessPopupListener()

        {

            public void reactOnPopupClosedByUser()

            {

                m_popup.close();

            }

        };

        m_popup.open("/eclntjsfserver/popups/onlinehelpjbrowser.jsp",

                     "Online Help",400,300, mpl);

        m_popup.setUndecorated(true);

        m_popup.setCloseonclickoutside(true);

        m_popup.setStartfromrootwindow(false);

        CLog.L.log(CLog.LL_INF,"Showing online help: " + url);

    }

 

}

 

The URL is assembled, and a modeless popup is called. The modeless popup itself is the one to hold the JBROWSER component.

Because the JBROWSER component is used for rendering the HTML text on client side, pay attention to the fact that quite complex HTML may not be rendered correctly. The JBROWSER component is sufficient for simple, static HTML (no JavaScript...) only.

Old Default Implementation “OnlineHelpProcessor”

There is an old implementation as well, which used the class “OnlineHelpProcessor”, that itself implements “IOnlineHelpProcessor”. This implementation does not send a URL to the client but the full online text itself. The online text then is passed into a TEXTPANE component – the same one that is used inside JBROWSER component.

This implementation is even more restrictive when it comes to what you can do with HTML. E.g. it fails to interpret and META-header parameter within an HTML document. And the component has problems with resolving references (e.g. to images).

We recommend the usage of the “OnlineHelpProcessor” in cases, in which you want to send plain text or RTF-formatted text to the frontend. For cases, that are HTML-related we clearly recommend to use the default “OnlineHelpProcessorJBrowser”.

Inside the OnlineHelpProcessor implementation there is an additional interface in order to resolve the text for the online help:

The interface is defined as follows:

package org.eclnt.jsfserver.onlinehelp;

 

public interface IOnlineHelpReader

{

    public class OnlineHelpText

    {

        String i_content;

        String i_type;

        public OnlineHelpText(String type, String content)

        {

            super();

            i_content = content;

            i_type = type;

        }

        public String getType() { return i_type; }

        public String getContent() { return i_content; }

    }

    

    public OnlineHelpText readOnlineHelpText(String helpId, String language);

}

 

The implementation has to return an OnlineHelpText-object which contains the following information:

The text will be displayed within a popup dialog.

Workplace Framework

Basics

Purpose

The user interface of typical applications consists out of a collection of individual functions. Each function is represented by some screen definition, the screen may of course itself subdivide into several other screens and may open various dialogs.

...so far you looked on Enterprise Client as technology to build such functions.

What you now need is some framework, to arrange these functions in order to form a workplace for an individual user. This includes the following aspects:

The workplace framework of Enterprise Client is exactly serving these requirements. It is an optional framework, so you do not have to use it! But it definitely makes sense to take a deep look into it before coding your own workplace framework.

Example – The Demo Workplace

The demo workplace is started with two container areas, one on the left one on the right.



The user can start functions from several function trees, the functions are by default started within the right container area. For each function a “tab” is shown at the bottom of the container area.

By dragging and dropping the user can re-arrange the started functions and open up new container areas:



The user can isolate functions into own dialogs, so that they are running in a decoupled window (and e.g. can be moved on a different physical screen).

Terms

When talking about the workplace, then certain terms will be frequently used:



A workplace holds one ore more workpage containers.

A workpage container holds one or more workpages.

A workpage is a normal Enterprise Client page started within the context of the workplace.

Development Effort

Basically there is no effort at all to start all pages that you have developed within the context of a workplace. It just works...

But: you typically want to make use of the workplace APIs. Examples:

In theses situations you want to talk to the workplace directly.

Creating your first Workplace

...all the information that you need in order to build your first workplace was outsourced into a separate tutorial “Building a workplace”.

We strongly recommend to process this tutorial so that you have some first, working workplace in which you can embed functions and in which you can test if you access the API in the right way.

Dispatcher Concepts

This chapter is the toughest one. It explains how the workplace internally works. On the one hand you do not need to know too much about these internal things, because at the end you do not see them during development.

On the other hand they are essential for some deep understanding, especially when it comes to understanding how the objects are separated from one another on server side.

The Dispatcher so far...

You got to know the Dispatcher class/object so far by being a factory object that is “always” written in front of the bean class that you address from a page.

Example: your page “order.jsp” binds a certain field via expression “#{d.OrderUI.orderNumner}” to the server side processing.

We already explained that the dispatcher is some kind of Map-instance, having a certain strategy for resolving names – if they are not contained in the map already. So if the map is asked for “OrderUI” and does not know a corresponding object yet, then the map is creating an OrderUI-object and adds it to its data.

The Dispatcher in the Workplace Context

Now comes the workplace...:

The workplace uses a dispatcher “WorkpageDispatcher” which is an enriched version of the normal dispatcher. It not only can resolve class names into objects, but it also can manage sub-dispatchers.

This sounds complex but indeed isn't. If a name to be resolved starts with “d_” then the dispatcher does not resolve this name as usual, but creates a new “WorkpageDispatcher” instance and manages this instance within its map. This instance is referred to as sub dispatcher in the following text.

When creating the instance of the sub dispatcher, then the same dispatcher-class is used than the one creating the dispatcher. E.g. if you derive your own dispatcher from “WorkpageDispatcher”, then the same class will be used for building the sub-dispatchers addresses via “d_”-names.

You may already see: the dispatcher – which actually is a factory – is now able to manage sub dispatchers, each one being a factory on its own. The workplace now is responsible for building one sub-dispatcher for each workpage.

Example: in the following workplace 6 workpages are started:



The workpages are: “Functions”, “Search”, “Inbox”, “Hello World”, “Mini Spreadsheet”, “Mini Spreadsheet”.

How is this represented within the objects on the server side? - The object are structured the following way:

d                                   <== the root dispatcher

.d_1                           <== subdispatcher instance

    .FunctionsUI                <== functions bean

.d_2                           <== subdispatcher instance

    .SearchUI                   <== search bean

.d_3                           <== subdispatcher instance

    .InboxUI                    <== inbox bean

.d_4                           <== subdispatcher instance

    .HelloWorldUI               <== hello world bean

.d_5                           <== subdispatcher instance

    .SpreadSheetUI              <== spreadsheet bean

.d_6                           <== subdispatcher instance

    .SpreadSheetUI              <== spreadsheet bean

 

The root dispatcher is holding 6 subdispatchers. In each subdispatcher the corresponding page bean is referenced.

Each workpage is internally associated with one subdispatcher, so that the objects between workpages are never shared. Especially this is important when looking into the “Mini Spreadsheet”, which is started twice in the workplace: there is one instance per workpage. As consequence there never is some conflict between the user processing the first and the second instance.

Addressing the correct Dispatcher

The workplace is showing workpages in dedicated areas – called workpage containers. Each workpage is bound to a screen definition – the screen that is started with the workpage. Basically the workplace management does nothing else then managing which workpage is currently shown in which workpage container.

In the example above one of the “Mini Spreadsheet” instances is just shown to the user on the right side of the workplace. The workplace loads the corresponding page and while loading tells the page to update all contained expressions from “#{d.*}” to “#{d.d_5.*}”. As consequence the expressions in the page that originally are directly pointing to the root dispatcher and then to the bean are updated “on the fly” - and now are pointing to the correct subdispatcher.

This is done completely automatically.

Accessing the Workplace Environment from your Bean

In case that you are interested to get to know the dispatcher that is responsible for managing your bean, you need to inherit your bean class from the following base classes:

Both provide constructors into which the “owning” dispatcher of the bean is passed as parameter.

public MyXXXXBean extends WorkpageDispatchedPageBean

{

    public MyXXXXBean(IWorkpageDisatcher dispatcher)

    {

        super(dispatcher);

        ...

    }

 

    private void yyyy()

    {

        ...

        getOwningDispatcher();

        ...

        getWorkpage();

        ...

    }

}

 

As shown in the code above, you at any point of the class can access the core objects of the workplace environment:

Using the Dispatcher as Context – or as bridge to your Context

In the text above you saw, that the Dispatcher is some essential player within the workplace framework.

When creating a CaptainCasa project then automatically a Dispatcher.java source file is created that provides a Dispatcher implementation:

package managedbeans;

 

import org.eclnt.workplace.IWorkpageContainer;

import org.eclnt.workplace.WorkpageDispatcher;

 

public class Dispatcher extends WorkpageDispatcher

{

    public static DispatcherInfo getStaticDispatcherInfo()

    {

        return new DispatcherInfo(Dispatcher.class);

    }

    protected String getRootExpression()

    {

        return "#{d}";

    }

    

    public Dispatcher()

    {

    }

 

    public Dispatcher(IWorkpageContainer workpageContainer)

    {

        super(workpageContainer);

    }

}

 

This class provides two constructors:

The dispatcher, which is some kind of context, now can be extended in any way you like in order to match your requirements.

Extending your Dispatcher

Example: you may want to keep the information about the currently logged on user:

package managedbeans;

 

import org.eclnt.workplace.IWorkpageContainer;

import org.eclnt.workplace.WorkpageDispatcher;

 

public class Dispatcher extends WorkpageDispatcher

{

    ...

    ...

    see above

    ...

    ...

    

    String m_user;

    public String getUser() { return m_user; }

    public void setUser(String user) { m_user = user; }

    

}

 

You logon page may access its context and write the corresponding info into the dispatcher:

public class LogonUI

    extends WorkpageDispatchedPageBean

{

    public LogonUI(IWorkpageDispatcher dispatcher)

    {

        super(dispatcher);

    }

 

    

    public void onLogonAction(ActionEvent event)

    {

        ...

        ...

        ((Dispatcher)getOwningDispatcher()).setUser(m_user);

        ...

        ...

    }

}

 

Now, the information about the logged on user is “such global” within your processing, that you need to make sure that it is not handled on sub-dispatcher level only, but it should be handled on root-dispatcher level. So the following implementation of your Dispatcher class is the corresponding improvement:

package managedbeans;

 

import org.eclnt.workplace.IWorkpageContainer;

import org.eclnt.workplace.WorkpageDispatcher;

 

public class Dispatcher extends WorkpageDispatcher

{

    ...

    ...

 

    public String getUser()

    {

        if (getOwner() == null)

            return m_user;

        else

            return ((Dispatcher)getOwner()).getUser();

    }

    public void setUser(String user)

    {

        if (getOwner() == null)

            m_user = user;

        else

            ((Dispatcher)getOwner()).setUser(user);

    }

}

Introducing some own Context

The Dispatcher can now be extended to hold the user name, the database name, the tenant id, etc. etc. - and you can access this infor from all page beans within your workplace (if deriving them from WorkpageDispatchedPageBean).

So it makes sense to structure a bit better – and to introduce some own context object, that keeps ally the information you define to be relevant:

public class DialogContext

{

    String m_user;

    String m_databaseName;

    String m_tennantId;

    

    

    public String getUser() { return m_user; }

    public void setUser(String user) { m_user = user; }

    public String getDatabaseName() { return m_databaseName; }

    public void setDatabaseName(String databaseName) { m_databaseName = databaseName; }

    public String getTennantId() { return m_tennantId; }

    public void setTennantId(String tennantId) { m_tennantId = tennantId; }

}

 

The improved Dispatcher implementation then is:

public class Dispatcher extends WorkpageDispatcher

{

    ...

    …

 

    DialogContext m_context;

    

    public Dispatcher()

    {

        m_context = new DialogContext();

    }

 

    public Dispatcher(IWorkpageContainer workpageContainer)

    {

        super(workpageContainer);

    }

    

    public DialogContext getContext()

    {

        if (getOwner() == null)

            return m_context;

        else

            return ((Dispatcher)getOwner()).getContext();

    }

    

}

 

Now you can access your context information from “everywhere” within the workplace. So if you have a class that is opened by the workplace (e.g. from the function tree of the wokrplace), then the corresponding code is:

public class HelloWorldUI

    extends WorkpageDispatchedPageBean

{

 

    public HelloWorldUI(IWorkpageDispatcher dispatcher)

    {

        super(dispatcher);

        DialogContext context = ((Dispatcher)dispatcher).getContext();

    }

 

    public String getPageName() { return "/helloworld.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.HelloWorldUI}"; }

}

The Workplace API

The three core interfaces of the workplace management are accessible from the page bean that is started within the workplace context as shown in the previous chapter. For detailed information of each method of the interfaces please check the JavaDoc documentation.

IWorkpageContainer

public interface IWorkpageContainer

{

    public void addWorkpage(IWorkpage workpage);

    public void switchToWorkpage(IWorkpage wp);

    public IWorkpage getWorkpageForId(String workpageId);

    public void closeWorkpage(IWorkpage workpage);

    ...

    ...

}

 

This interface gives access to the workpage container, managing workpages.

When starting a workpage then part of the workpage information (IWorkpage) is always an id. The id is a string, that is built up by yourself – so you can use any semantics that is suitable to you. You use the id to identify a certain page in the workpage container.

Example: when you start a workpage showing an article 4711, then your id might be “ARTICLE_4711”. Maybe the article 4711 already is displayed as workpage within your workplace? So you do not want to show the page twice, but better want to switch to the workpage already displaying the article?

In this case you first check with “getWorkpageForId(“ARTICLE_4711”)” if there is already a workpage. If so you use “switchToWorkpage(...)” to bring it to the front, if not you create a new workpage. The creation is done on lower level by passing an instance of IWorkpage, using the default implementation “Workpage”.

Or you use a helper interface “IWorkpageStarter”:

public interface IWorkpageStarter

    extends Serializable

{

    public IWorkpage startWorkpage(IWorkpageDispatcher workpageDispatcher,

                                   IWorkpageContainer workpageContainer,

                                   WorkpageStartInfo startInfo);

}

 

You obtain an instance of IWorkpageStarter by calling WorkpageStarterFactory.getInstance().

With IWorkpageStarter you pass an instance of “WorkpageStartInfo”, which is a class that is used everywhere in the workplace context, where you specify information about a page to be started. You may remember the tutorial, when defining e.g. the function tree or the defaul perspective: there you defined WorkpageStartInfo instances using the JAX-B-XML representation, now you do it in the interface “by bean”.

WorkpageStartInfo

The definition that is required for at runtime starting a page is kept in an object of type “WorkpageStartInfo”. Once started, an instance of “IWorkpage” is built and registered within the workplace. So “WorkpageStartInfo” holds the information about how to start a page, whereas “IWorkpage” holds the information about an actual instance of a started page.

Core Functions

WorkpageStartInfo contains the following information:

There is a clase “WorkpageStartInfo” and an interface “IWorkpageStartInfo” which collects all this information:

public interface IWorkpageStartInfo

{

    public void setJspPage(String value);

    public void setPageBeanName(String value);

    public void setImage(String value);

    public void setText(String value);

    public void setDecorated(boolean decorated);

    public void setOpenMultipleInstances(boolean openMultipleInstances);

    public void setId(String value);

    public void setParam(String paramName, String paramValue);

    public void removeParam(String paramName);

    ...

    ...

}

 

Special Usage - Starting Functions

Normally you define within the WorkPageStartInfo-instances which page to start.

You in addition can define that instead of starting a page a certain internal function is invoked. In this function you can “do what you want”.

You do so by passing a class name via the “setFunctionClassName(...)” method of WorkpageStartInfo. A new instance of the class will be created when the user starts the corresponding item. The instance is expected to support interface “IWorkpageFunctionExecute”. Example:

public class DemoWorkpageFunction implements IWorkpageFunctionExecute

{

 

    public IWorkpage executeFunction(IWorkpageDispatcher rootDispatcher,

                                     IWorkpageStartInfo workpageStartInfo)

    {

        ...

        ...

        return null;

    }

 

}

IWorkpage / Workpage

When starting a workpage (e.g. by double clicking into the function tree), then a corresponding object is created within the workplace framework. The object supports interface “IWorkpage” - the default implementation is class “Workpage”.

The interface IWorkpage is:

public interface IWorkpage

{

    public String getJspPage();

    public String getTitle();

    public String getIconURL();

    public String getId();

    public boolean isDecorated();

 

    public void setWorkpageContainer(IWorkpageContainer container);

    public IWorkpageContainer getWorkpageContainer();

 

    public IWorkpageDispatcher getDispatcher();

    public String getParameter(String name);

 

    ...

    ...

}

 

The work page is associated with certain information that the workplace needs in order to draw a titlebar for the work page and in order to list the page in the task bar:

Start Parameters

There is also the possibiliy to define start parameters when defining a workpage: the start parameters are simple String-objects (for good reason we do not allow complex objects to be passed...).

The typical procedure of passing start parameters is:

The application can take over the parameters by accessing the workpage using method “getWorkpage()”, reading the parameters and preparing its data content accordingly.

Example: in the function tree you add the function to show article with id “4711”. As consequence you create a WorkpageStartInfo instance (by bean, by XML) in which you define parameter “ARTICLEID” to be “4711”. The code to open the page in the workplace may look as follows:

...UI bean of the starting bean, e.g. list of articles...:

 

    ...

    WorkpageStartInfo wpsi = new WorkpageStartInfo();

    wpsi.setId(“ARTICEL_4711”);

    wpsi.setPageBeanName(“ArticleDisplayUI”);

    wpsi.setText(“Article Display 4711”);

    wpsi.setParam(“ARTICLEID”,”4711”);

    ...

    IWorkpageStarter wps = WorkpageStarterFactory.getWorkpageStarter();

    wps.startWorkpage(getOwningDispatcher(),

                      getWorkpageContainer(),

                      wpsi);

    ...

 

The workpage is started, the ArticleDisplayUI instance is created – so in the constructor you read the start parameters in the following way:

public class AritcleDisplayUI extends WorkpageDispatchedPageBean

{

    public ArticleDisplayUI(IWorkpageDispatcher dispatcher)

    {

        super(dispatcher);

        String articleId = getWorkpage().getParam(“ARTICLE”);

        initialize(articleId);

    }

 

    private void initialize(String articleId)

    {

        ...

        ...

        ...

    }

}

Workpage Life Cycle Aspects – Closing of a Workpage

When a workpage is started (e.g. double click in the function tree) then a corresponding “IWorkpage” object is created, itself being linked to a corresponding dispatcher object that manages the UI beans for the workpage's context.

This is the starting procedure. But: the workplace management also manages the ending of a workpage: e.g. the user presses the “top right close button” of the workpage. The sequence of operations is as follows:

The interface contains two important methods:

package org.eclnt.workplace;

 

public interface IWorkpageLifecycleListener

{

    ...

    public boolean close();

    public void closeForced();

    ...

}

 

There is a second method “closeForced()”. In this method the listeners just have to close – there is no chance to escape.

Workpage Lifecycle Aspects – Additional Information

Please take a detailed look into the IWorkpageLifecycleListener interface, it also contains very useful other methods:

package org.eclnt.workplace;

 

public interface IWorkpageLifecycleListener

{

    // ...the ones from the previous chapter...

    public boolean close();

    public void closeForced();

    

    public void reactOnDestroyed();

 

    public void reactOnShownInContentArea();

    public void reactOnHiddenInContentArea();

    public void reactOnShownInPopup();

}

 

The most important method is the method “reactOnDestroyed()”. This is called in two situations:

You should use the reactOnDestroyed() method for tidying up resources,

Inter Workpage Eventing

The workplace framework allows to start functions in so called workpages. Each function's managed beans are isolated from other functions' managed beans so that there is a maximum level of isolation between: each workpage is running “on its own” without getting disturbed by other workpages running in parallel.

Now, there are certain situations in which you explicitly want one function of the workplace to talk to another function. For example there is a list of objects started as one function and a detail processing of an object started as a second function. As soon as the user updates the detail, the list should be updated as well.

Of course there is always the possibility to use some “model type of eventing” below the managed beans: i.e. if list and detail are talking to the same model then both can be updated by corresponding model events. But this in reality is not always available...

So what we explain in this chapter, is the possibility to let one function (workpage) talk to other functions (workpages) – but still following the concept of isolation between functions.

The solution is “by eventing”: there is a event processing that is added to the workpage management. You can throw events from one workpage and process these events on other workpages. In the demo workplace there is an example showing this:

Two workpages are opened in parallel, one isolated as popup. When the user presses the button “Execute” on the foreground workpage then a grid item is added inside the background workpage.

Take a look onto the event sender:

    public void onCreateItem(ActionEvent event)

    {

        DemoWpAddItemEvent workpageEvent = new DemoWpAddItemEvent();

        getWorkpage().throwWorkpageProcessingEvent(workpageEvent);

    }

 

The method onCreateItem() is called every time the user presses the button. It creates an instance of an event class and passes it to the workpage's method “throwWorkpageProcessingEvent()”. The event class itself looks the following way:

public class DemoWpAddItemEvent

    extends WorkpageProcessingEvent

{

}

 

It just derives from “WorkpageProcessingEvent” which comes with the CaptainCasa server processing. Of course your implementation may contain additional members and methods that you want to pass with a certain event.

The receiver screens has the following code:

public class DemoWpReceive

    extends WorkpageDispatchedBean

    implements Serializable

{

    

    public class MyWorkpageProcessingEventListener

        implements IWorkpageProcessingEventListener

    {

        public void processEvent(WorkpageProcessingEvent event)

        {

            if (event instanceof DemoWpAddItemEvent)

                addNewItem();

        }

    }

    

    public class MyRow extends FIXGRIDItem implements java.io.Serializable

    {

        protected String m_lastName;

        public String getLastName() { return m_lastName; }

        public void setLastName(String value) { m_lastName = value; }

        

        protected String m_firstName;

        public String getFirstName() { return m_firstName; }

        public void setFirstName(String value) { m_firstName = value; }

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    protected FIXGRIDListBinding<MyRow> m_rows = new FIXGRIDListBinding<MyRow>();

    

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    public DemoWpReceive(IDispatcher dispatcher)

    {

        super(dispatcher);

        getWorkpage().addWorkpageProcessingEventListener

            (new MyWorkpageProcessingEventListener());

    }

    

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    public FIXGRIDListBinding<MyRow> getRows() { return m_rows; }

    

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

    

    private void addNewItem()

    {

        MyRow row = new MyRow();

        row.setFirstName("New first name");

        row.setLastName("New last name");

        m_rows.getItems().add(row);

    }

    

}

 

It contains an event listener class/object that implements the interface IWorkpageProcessingEventListener. The listener gets called by the workplace processing every time a workplace event is thrown somewhere on another workpage. The listener checks if the event is relevant for the current page and executes a corresponding method.

Result: a coupling between two workpages is defined – without hard-wiring the processing of both pages.

Addon Components in the Workplace Management

There are some additional add on components that are available within the workplace framework.

Workplace Perspective Management

So far you have seen:

Now, all this is brought together into a quite powerful framework on top of this: the workplace perspective management.

What the Workplace Perspective Management does

The basis of the workplace perspective management is the capability to run multiple workpage containers in parallel. The user can – by simply dragging and dropping move functions from the one container into the next. The user can split up workpage containers into new ones and as result can flexibly arrange a workpage container environment that fits to his/her needs.

Example: the demo workplace is started with the following default perspective:



There are two areas: one on the left, holding the function tree and some inbox management – and one on the right, so far not holding any page (the background page is shown).

After starting some functions, the user can drag&drop the corresponding workpages by picking them within their title area – or by picking the corresponding selector item. The result may look like:



The “perspective” now contains four areas, one of them (the central) one, being the “root” area. A “perspective” is the definition of a certain arrangement of workpage containers, together with the information what page is started within a workpage container.

The perspective management can either be used by API or it can be used by configuration. Within the configuration it is possible to...:

The following text will guide you through the steps to create such a “perspective-driven” workplace environment.

The WORKPLACE Page

The first step is to create a page that actually holds the workplace. This is quite simple – just define the following page:

<t:rowbodypane id="g_1" background="#808080">

    <t:rowworkplace id="g_2" objectbinding="#{d.workpageContainer}"

        wpselectorposition="bottom" />

</t:rowbodypane>

<t:rowstatusbar id="g_3" />

 

The central component is the ROWWORKPLACE component. It points via its OBJECTBINDING attribute to the workpage dispatcher's property “workpageContainer”.

When previewing the page the result looks like:



You see two workpage container areas, one on the left holding “Page 1”, one on the right holding no page. ...well yes, if we say “You see...” then actually you see some info about a missing page on the left, and some empty area on the right – but this is OK (up to now).

The information comes out of a default configuration that splits the workplace into two parts, and that opens “Page 1” on the left side.

Please note: you can create a workplace page by using a corresponding template when creating the page in the Layout Editor:



In this case the page will look like:



Some background coloring and some additional useful components are arranged in addition to the central ROWWORKPLACE component.

Configuring a Perspective

Now, let's update this perspective. To do so, open the “Runtime Configuration” tool within the layout editor's tool area (on the right):



There are three sections of configuration:

Inside the “Perspectives” section you see a file “default.xml”. By double clicking you can open and edit the file content. The default content is:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<workplaceTileInfo>

    <text>Demo Workplace</text>

    <split>

        <orientation>horizontal</orientation>

        <dividerLocation>250</dividerLocation>

        <subContainer1>

            <id>FUNCTIONS</id>

        </subContainer1>

        <subContainer2/>

    </split>

    <startViews>

        <jspPage>/page1.jsp</jspPage>

        <id>page1</id>

        <text>Page 1</text>

        <decorated>true</decorated>

        <closeSupported>true</closeSupported>

        <popupSupported>true</popupSupported>

        <openMultipleInstances>false</openMultipleInstances>

        <startSubWorkpageContainerId>FUNCTIONS</startSubWorkpageContainerId>

    </startViews>

</workplaceTileInfo>

 

A perspective configuration always consists of two parts:

You may update the file to your needs. Internally the XML definition that you edit is a JAXB binding to class “WorkplaceTileInfo”. Please consult the JavaDoc for details what configuration is possible to be done within the XML file.

Please note: the runtime configuration that you edit, is internally stored within the /webcontent/eclntjsfserver/config/ccworkplace directory of your project. You can also edit the information directly within your development environment (Eclipse, …).

You need to reload your application in order to bring changes from your project into the runtime.

Configuring multiple Perspectives for multiple Users

After now having maintained the first perspective you can set up multiple perspectives, either to be used by one user (user may switch between different perspectives) or to be used by several users.

The way to do is: Just create new perspective definitions within the “Perspectives” section of the runtime configuration. Then edit the files within the “User Info” section. The default configuration file that is chosen is “undefined.xml”:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<workplaceUserInfo>

    <perspectives>default</perspectives>

    <defaultPerspective>default</defaultPerspective>

    <functionTree>default</functionTree>

    <backgroundPage>/empty.jsp</backgroundPage>

</workplaceUserInfo>

 

Within the file you may define multiple “perspectives” to be used. These are the “potencial” perspectives for one user. And there is one “defaultPerspective” which is the one that is opened by default.

How is this user info read at runtime?

The system calls interface “IUserAccess” in order to ask your application what the id of the currently logged on user is. With the result it tries to find a corresponding definition “<userId>.xml” within the “User Info” section.

When not explicitly defining an “IUserAccess” interface then a default/dummy one is used, returning user “undefined” by default – this is the reason, why “undefined.xml” is selected in the default environment.

Switching between different Perspectives

When defining multiple perspectives to be available for a certain user, then you may use a nice component WORKPLACEPERSPECTIVESELECTOR to switch between the perspectives.



The layout definition is:

<t:rowbodypane id="g_1" background="#808080">

    <t:row id="g_2">

        <t:coldistance id="g_3" width="100%" />

        <t:workplaceperspectiveselector id="g_4"

            objectbinding="#{d.workpageContainer}" />

        <t:coldistance id="g_5" width="100" />

    </t:row>

    <t:rowdistance id="g_6" height="20" />

    <t:rowworkplace id="g_7" objectbinding="#{d.workpageContainer}"

        wpselectorposition="bottom" />

</t:rowbodypane>

<t:rowstatusbar id="g_8" />

<t:pageaddons id="g_pa"/>

 

The component is bound via its OBJECTBINDING attribute to the “workpageContainer” of your workpage dispatcher.

As you may have seen already, the menu that allows to switch between different perspectives also provides a section “Manage Perspectives...”. The purpose behind is:

Loading the Workplace Perspective when changing the User

When changing the user then you have to tell the workplace perspective management about. This is done by calling the following function:

    IWorkpageDispatcher

    .getWorkpageContainer()              // passes IWorkpageContainer

    .prepareWorkplaceForCurrentUser()

 

So this is the function to be called after e.g. an explicit log on of a user.

Workplace Perspective – Low Level API

So far you saw in this chapter how to set up a workplace using a configuration via XML definitions. This is the “automated and most comfortable” way of using the workplace perspective management.

Of course you can directly access the workplace perspective management via interfaces. The most important issues are:

In your dispatcher implementation you can switch off the automatic functions by creating a WorkpageContainer-instance through the following constructor:

    /**

     * This constructor may be used if you want to switch off the usage

     * of the default workplace management. The default workplace management

     * reads the runtime configuration for the current user and creates

     * a corresponding workplace perspective.

     */

    public WorkpageContainer(IWorkpageDispatcher dispatcher,

                             boolean withPreparingWorkplaceForCurrentUser)

    {

        ...

    }

 

For creating an own instance you need to override the dispatcher's createWorkpageContainer() method, e.g. in the following way:

    protected IWorkpageContainer createWorkpageContainer()

    {

        return new WorkpageContainer(this,false);

    }

 

Now there is “no automated magic” anymore, but you can directly access the interfaces. The workpage container (IWorkpageContainer) provides access to the perspetice management by its tile manager:

public interface IWorkpageContainer

{

    ...

    public WorkplaceTileManager getTileManager();

    ...

}

 

The WorkplaceTileManager e.g. allows to render a certain layout that you pass inside via the method:

public class WorkplaceTileManager

{

    ...

    public void importWorkplaceTileInfo(WorkplaceTileInfo tileInfo)

    {

        ...

    }

    ...

}

 

Please used these hints and the dive into the JavaDoc documentation for the corresponding classes.

Workplace Functions Management

Function trees are the typical tree structure of a workplace, providing all call-able functions for a certain user. You can either define function trees by program API or you can define function trees by XML definition. The XML that you define is a pure JAX-B XML representation of the API object that you pass by API.

This means: the functions management is exactly the same for both configuration scenarios. You may start to configure the function tree by XML and then later on decide to make it dnyamic, e.g. base on user- and role-specific authority definitions.

...by XML declaration

There are certain steps to set up a function tree, that is managed “by declaration”:

Creation of Page to hold Function Tree(s)

The easiest way to create a page to hold the function trees is to use a certain template when creating the page in the Layout Editor:



After creating the page you will see the following preview in the Layout Editor:



Well, this looks not too nice – but remember: some more colorful background typically is provided “behind” the page when being used, so things will look much nicer then.

<t:row id="g_1">

    <t:pane id="g_2" background="#00000010" height="100%" width="100%">

        <t:rowworkplacefunctions id="g_3"

            objectbinding="#{d.workpageContainer}" />

    </t:pane>

</t:row>

 

The central component is the ROWWORKPLACEFUNCTIONS component – pointing to the dispatcher's workpage container via its OBJECTBINDING attribute.

The component checks the current user (IUserAccess interface), and loads the function tree that is defined for this user. By default there are some dummy function tree definitions already available – that's where the content you see is from.

Set up Function Trees

Function trees are set up within the runtime configuration – very similar to the definitions in the perspective management:



A function tree is a hierarchical arrangement of node definitions. Each node may either be a “folder node” - i.e. it contains a numer of sub-nodes. Or it may be a “page node”, i.e. it is associated with some information about which page to start.

There is a special type of node definition: nodes, that point to other function tree definition (in the default project this is the “default.xml” definition). By using these nodes you can build up function trees out of several other function trees – you do not have to embed all functions for a certain scenario into one tree definition, but can split the information into several re-usable tree definitions.

When defining function trees, please note: the first level of node definitions is always used to form the “outlook bar”.

Assign Function Tree to User

The assignment is done within the user info section – each user points to one function tree.

...by API

The first step is the same as with the XML definition – you need to define a layout page to keep the function tree. So follow the steps for creating the function tree page that are listed in the previous chapter.

But now you do not define some XML function tree definition, but you use an API. Example:

for (int i=0; i<5; i++)

{

    WorkplaceFunctionTreeInfoNode n2 = new WorkplaceFunctionTreeInfoNode();

    n2.setText("Node Level 1 / Instance " + i);

    n1.getSubNodes().add(n2);

    for (int j=0; j<5; j++)

    {

        WorkplaceFunctionTreeInfoNode n3 = new WorkplaceFunctionTreeInfoNode();

        n3.setText("Node Level 2 / Instance " + j);

        for (int k=0; k<5; k++)

        {

            WorkplaceFunctionTreeInfoNode n4 = new WorkplaceFunctionTreeInfoNode();

            n4.setText("Node Level 3 / Instance " + k);

            WorkpageStartInfo wpsi = new WorkpageStartInfo();

            n4.setWorkpageStartInfo(wpsi);

            wpsi.setJspPage("/workplace/demohelloworld.jsp");

            n3.getSubNodes().add(n4);

        }

        n2.getSubNodes().add(n3);

    }

}

((WorkpageContainer)getWorkpageContainer()).getFunctionsManager().importWorkplaceFunctionTreeInfoNode(n1);

((WorkpageContainer)getWorkpageContainer()).getFunctionsManager().setObIndex(0);

 

The program is creating the following function trees:

Please check the demo “General > Workplace Management > Modify Function Tree at Runtime” within the demo workplace.

Multi-screen workplaces

Basics

In certain environments users want to take benefit of working with multiple screens. For example they run overview and monitoring type of functions on one screen and operational functions with data input and output on the other screen.

A workplace (as any other CaptainCasa dialog) is running inside one browser instance. A browser instance may be an individual browser or a browser tab. - One browser instance cannot be spanned across multiple screens, so the natural way of running a web application across multiple screens is to start it two times and shifting the second browser instance to the second screen.

In this case, of course, the user works with two browsers, which are not synchronized with one another in any means.

With update 20210823 CaptainCasa introduced the support of multi-screen workplace scenarios and provides a simple to use API to setup a synchronization between workplace instances.

The functions include:

The individual workplaces are synchronized automatically: if the user e.g. starts a function from the “left” into the “right” workplace, then the “right” workplace immediately is updated, without requiring any activity by the user. This synchronization is internally done by the use of a web socket based communication.

Use ROWWORKPLACE!

The typical way to use the workplace management is to use the ROWWORKPLACE component. When working with this component then all UI parts which are required in the multi-screen scenarios are automatically created.

If not using this component, but if using the individual components for building the workplace (ROWWORKPAGECONTAINER and ROWWROKAGESELECTOR) then you need to add certain components to your workplace. These components to be added are described later.

We definitely recommend to use ROWWORKPLACE, of course!

API-facade “MultiWorkplaceConnector”

The class “MultiWorkplaceConnector” is the central facade for accessing the multi-screen functions. You need to call the “instance()” method in order to access the central instance that you then use for calling:

Please check the demos in the demo workplace and the JavaDoc for concrete coding hints.

Workplace naming management

From one browser running a workplace you can start multiple other browsers starting dependent workplace. Each dependent workplace receives a name (“DETAIL”). The multi-screen workplace management makes sure that only one instance per name can actually be started.

From the starting workplace you can trigger functions in the dependent workplace by passing its name. Example:

MultiWorkplaceConnector.startWorkpage(IWorkpageDispatcher fromWorkplace, WorkpageStartInfo wpsi, String targetWorkplaceName)

 

From the dependent workplace you can trigger functions in the original workplace it was started from by using the pre-defined name “ccstarter” (please use constant MultiWorkplaceConnector.CCSTARTER).

Multi-screen eventing

Inside a “normal” workplace, there is an eventing mechanism on workplace level: a dialog can throw an event of type “WorkageProcessingEvent” - other dialogs can register events by “IWorkpage.addWorkpageProcessingEventListener(...)”. The same processing is used for multi-screen eventing as well: one page (e.g. on the dependent screen) throws an event (e.g. “data updated”), other pages (e.g. on the starting screen) listens and reacts accordingly (e.g. “refresh list”).

The normal event listener implementation...

public interface IWorkpageProcessingEventListener

{

    public void processEvent(WorkpageProcessingEvent event);

}

 

...is dedicated to events that are distributed within one workplace.

By just using the extended version of the interface...

public interface IMultiWorkplaceWorkpageProcessingEventListener extends IWorkpageProcessingEventListener

{

    public boolean checkIfEventIsRelevant(WorkpageProcessingEvent event);

}

 

...you now listen to both local workplace events and cross-screen events. There is just one additional method “checkIfEventIsRelevant” that you need to implement.

Why is there an additional method for multi-screen events?

The reason is: the reaction on the event, i.e. the calling of method “processEvent”, is always done in a request/response-thread of the client. In the local workplace environment this is obvious because all the processing is done as part of the normal request/response-processing. In a multi-screen scenario the event is thrown e.g. in the processing of the dependent workplace – and the event processing is executed in the original screen. As consequence there is some internal mechanism, that ensured that the processing is done in a request/response-processing of the original screen. Internally a web-socket polling mechanism is used to do so.

Please check the demo in the demo workplace for concrete coding information.

Creating a “reduced workplace”

As already shown in the previous screenshot...

...the workplace that is started as dependent workplace may be a reduced workplace without function tree or other workplace functions.

The layout is:

<t:rowtitlebar id="g_2" text="Side workplace" />

<t:rowbodypane id="g_9" padding="left:20;top:20;bottom:10">

    <t:rowworkplace id="g_11" objectbinding="#{d.workpageContainer}"

        wpselectorposition="bottom" />

</t:rowbodypane>

 

The code is:

package workplace;

 

import java.io.Serializable;

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.workplace.IWorkpageDispatcher;

import org.eclnt.workplace.WorkpageDispatchedPageBean;

import org.eclnt.workplace.WorkplaceTileInfo;

import org.eclnt.workplace.WorkplaceTileInfoPageContainer;

 

@CCGenClass (expressionBase="#{d.WorkplaceOneContainerOnlyUI}")

public class WorkplaceOneContainerOnlyUI

    extends WorkpageDispatchedPageBean

    implements Serializable

{

    public WorkplaceOneContainerOnlyUI(IWorkpageDispatcher workpageDispatcher)

    {

        super(workpageDispatcher);    

        loadEmptyWorkplace();

    }

 

    public String getPageName() { return "/workplace/workplaceOneContainerOnly.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.WorkplaceOneContainerOnlyUI}"; }

 

    private void loadEmptyWorkplace()

    {

        WorkplaceTileInfo ti = new WorkplaceTileInfo();

        WorkplaceTileInfoPageContainer tipc = new WorkplaceTileInfoPageContainer();

        ti.setContainer(tipc);

        getWorkpageContainer().getTileManager().importWorkplaceTileInfo(ti);

    }

}

 

Please take a look at the “loadEmptyWorkplace()”-method:

Session considerations

If one workplace starts a second workplace the both are running as completely separated browser instances – and as consequence as completely separated dialog sessions on server side.

Dependent on the session configuration of your application this means:

Please pay attention: both dialog sessions require to run in the same Java process!

If not using ROWWORKPLACE

If not having built your workplace dialogs with the ROWWORKPLACE-component but e.g. with separate ROWWORKPAGECONTAINER- and/or ROWWORKPAGESELECTOR-components, then certain components are not added to the workplace automatically, that are required to run the multi-screen workplace.

In this case you have to add the following to your workplace pages:

<t:sessioncloser/>

<t:showurl
   rendered=”#{d.workpageContainer.multiWorkplaceManager.active}”

    websocketurl=”#{d.workpageContainer.multiWorkplaceManager.startURL}”

    trigger=”#{d.workpageContainer.multiWorkplaceManager.startURLTrigger}”

/>

<t:webscoketpolling

    rendered=”#{d.workpageContainer.multiWorkplaceManager.active}”

    websocketurl=”#{d.workpageContainer.multiWorkplaceManager.webSocketURL}”

    actionListener=

        ”#{d.workpageContainer.multiWorkplaceManager.onRefreshAction}”

/>

 

Developing an own work page selector

Introduction

One work page container may hold several work pages. There is a default component ROWWORPAGESELECTOR that renders a corresponding selection area to let the user switch between the different work pages. This component is used internally when using the ROWWORKPLACE component, too:

By default the rendering is done as part of the workplace framework using a TABBEDPANE component.

The component is using some own style class definition for its usage inside the workplace, so you can flexibly change the styling independent from the normal TABBEDPANE styling. But of course you are bound to the component's general capabilities – showing an image, some text and a close icon.

Developing an own work page selector

You can overcome the limits of the TABBEDLINE by rendering the work page selector are completely on your own. All you have to do is:

Example

Let's assume, the rendering of the work page selector should be the following:

(The demo workplacea contains the implementation of this example. Please take a look into layout “/workplace/workplaceselector/DemoSelector.jsp” and code “/workplace/DemoSelector.java”.)

The layout of the selector is the following:

<t:beanprocessing id="g_1" beanbinding="#{d.DemoSelector}" />

<t:row id="g_2">

    <t:slidecontainer id="g_6"

        background="#{ccstylevalue['@baseColorDark@']}"

        width="100%">

        <t:pane id="g_3">

            <t:rowdynamiccontent id="g_5"

                contentbinding="#{d.DemoSelector.dynContent}" />

        </t:pane>

    </t:slidecontainer>

</t:row>

 

It contains a SLIDECONTAINER (so that the user can move the are with drag&drop), in which some dynamic content is embedded.

The code is:

package workplace;

 

import java.io.Serializable;

import java.util.ArrayList;

import java.util.List;

 

import javax.faces.event.ActionEvent;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.elements.componentnodes.COLDISTANCENode;

import org.eclnt.jsfserver.elements.componentnodes.ICONNode;

import org.eclnt.jsfserver.elements.componentnodes.PANENode;

import org.eclnt.jsfserver.elements.componentnodes.ROWDISTANCENode;

import org.eclnt.jsfserver.elements.componentnodes.ROWNode;

import org.eclnt.jsfserver.elements.componentnodes.TEXTPANENode;

import org.eclnt.jsfserver.elements.impl.ROWDYNAMICCONTENTBinding;

import org.eclnt.jsfserver.elements.impl.ROWDYNAMICCONTENTBinding.ComponentNode;

import org.eclnt.jsfserver.util.StyleManager;

import org.eclnt.workplace.IWorkpage;

import org.eclnt.workplace.IWorkpageContainer;

import org.eclnt.workplace.IWorkpageDispatcher;

import org.eclnt.workplace.IWorkpageSelector;

import org.eclnt.workplace.WorkpageDispatchedPageBean;

 

@CCGenClass (expressionBase="#{d.DemoSelector}")

 

public class DemoSelector

    extends WorkpageDispatchedPageBean

    implements Serializable, IWorkpageSelector

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

 

    public class ItemInfo

    {

        IWorkpage i_workpage;

        public ItemInfo(IWorkpage workpage)

        {

            i_workpage = workpage;

        }

        public String getBackground()

        {

            if (m_wpContainer.getCurrentWorkpage() == i_workpage)

                return StyleManager.getStyleValue("@baseColor@");

            else

                return StyleManager.getStyleValue("@baseColorDark@");

        }

        public String getText()

        {

            String s = i_workpage.getSelectorTitle();

            if (s == null)

                s = i_workpage.getTitle();

            return s;

        }

        public void onPaneAction(ActionEvent event)

        {

            m_wpContainer.switchToWorkpage(i_workpage);

        }

        public void onIconAction(ActionEvent event)

        {

            m_wpContainer.closeWorkpage(i_workpage);

        }

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    IWorkpageContainer m_wpContainer;

    ROWDYNAMICCONTENTBinding m_dynContent = new ROWDYNAMICCONTENTBinding();

    List<ItemInfo> m_itemInfos = new ArrayList<ItemInfo>();

 

    // ------------------------------------------------------------------------

    // constructors & initialization

    // ------------------------------------------------------------------------

 

    public DemoSelector(IWorkpageDispatcher workpageDispatcher)

    {

        super(workpageDispatcher);        

    }

 

    public String getPageName() { return "/workplace/workplaceselector/DemoSelector.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.DemoSelector}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

 

    public ROWDYNAMICCONTENTBinding getDynContent() { return m_dynContent; }

    public List<ItemInfo> getItemInfos() { return m_itemInfos; }

 

    @Override

    public void init(IWorkpageContainer container)

    {

        m_wpContainer = container;

    }

 

    @Override

    public void update()

    {

        List<ComponentNode> l = new ArrayList<ComponentNode>();

        List<IWorkpage> workpages = m_wpContainer.getAllWorkpages();

        m_itemInfos.clear();

        for (IWorkpage workpage: workpages)

        {

            m_itemInfos.add(new ItemInfo(workpage));

            PANENode p = new PANENode()

                    .setHeight("60+")

                    .setWidth("100")

                    .setPadding("5")

                    .setBorder("right:1;color:#FFFFFF20")

                    .setBackground(pbx("itemInfos["+(m_itemInfos.size()-1)+"].background"))

                    .setInvokeevent("leftclick")

                    .setActionListener(pbx("itemInfos["+(m_itemInfos.size()-1)+"].onPaneAction"))

                    .setDragsend("CCWORKPAGE:"+workpage.getUniqueTechnicalId());

            l.add(p);

            ROWNode r = new ROWNode();

            p.addSubNode(r);

            TEXTPANENode tp = new TEXTPANENode()

                    .setWidth("100%")

                    .setForeground("#F0F0F0")

                    .setFont("weight:bold")

                    .setText(pbx("itemInfos["+(m_itemInfos.size()-1)+"].text"));

            r.addSubNode(tp);

            if (workpage.isCloseSupported() == true)

            {

                p.addSubNode(new ROWDISTANCENode().setHeight("100%;5"));

                r = new ROWNode();

                p.addSubNode(r);

                r.addSubNode(new COLDISTANCENode().setWidth("100%"));

                ICONNode ic = new ICONNode()

                        .setImage("/eclntjsfserver/images/iconssvg/close_light_16x16.svg")

                        .setActionListener(pbx("itemInfos["+(m_itemInfos.size()-1)+"].onIconAction"));

                r.addSubNode(ic);

            }

        }

        m_dynContent.setContentNodes(l);

    }

}

 

What are the important issues?

"CCWORKPAGE:"+workpage.getUniqueTechnicalId()

 

All you have to do now is to register this own implementation of the work page selector in the system.xml configuration file:

<system ...>

     <workplace ...

                workpageselector="workplace.DemoSelector"

                ...

     />

</system>

 

Working with Macros

CaptainCasa Enterprise Client provides a so called Macro Management that can simplify the server side definition of JSP pages significantly. It is typically applied, when you have a quite generic way of passing data from and to your UI components

In short: a macro is a definition which allows to automatically set component attributes based on the macro's input parameters. The macro is executed at runtime on server side.

Example Use Case

A FIELD component provides a high number of attributes that you may use:

In an application you typically want to control all of these attributes dynamically:

Well, with the TEXT, it's obvious – this is the core data passed in and out. But the other attributes are important as well: dependent on the user's rights or dependent on a logical decision within your business logic you want to enable or disable the field. If the field's content is not correct you want to highlight the field. Etc. etc. ...

As consequence your server side managed bean provides all this information for the FIELD component. The field's attribute definition may look like:

<field id=”g_57”

       text=”#{bean.zipCode}”

       bgpaint=”#{bean.zipCodeBGPAINT}”

       enabled=#{bean.zipCodeENABLED}”

       width=”200”/>

 

This is just an example! You may also shift the “paint” and “enabled” information into parallel objects!

What you see from the FIELD component definition: it binds to the property “zipCode” - and all the information around is arranged in properties that are linked with “zipCode”.

It's now a hell of work to manually maintain all this information – expecting that there are many fields following the same naming pattern. And: in case you extend the naming and logic pattern (e.g. introducing a new property “#{bean.zipCodeFOREGROUND}” to rule the foreground color) you need to update all the FIELD definitions within your JSP pages.

That's where macros come in! A macro is a simple and consistent way of defining how to apply attribute values following a naming pattern. Sounds difficult, but is not at all!

Macro Definition

A macro is a Java object supporting interface “IMacro”. There are currently two way to create macro instances:

Macro Definition by XML

A macro is an XML definition that is stored in the directory “/eclntjsfserver/config/macros”. The file name that you assign is the macro's name.

Let's look onto the macro definition – using the name “prop.xml” - that simplifies the creation of FIELD components within the example use case scenario:

<macro>

    <applysto>

        <tag name="t:field"/>

        <tag name="t:combofield"/>

    </applysto>

    <parameters>

        <parameter name="property"/>

    </parameters>

    <attributes>

        <attribute name="text" value="#{bean.${property}}"/>

        <attribute name="bgpaint" value="#{bean.${property}BGPAINT}"/>

        <attribute name="enabled" value="#{bean.${property}ENABLED}"/>

    </attributes>

</macro>

 

The definition includes:

Macro Definition by Class Implementation

You can implement an own class supporting interface “IMacro”.

Example:

package demomacros;

 

import org.eclnt.jsfserver.elements.BaseComponentTag;

import org.eclnt.jsfserver.elements.macros.IMacro;

 

public class DemoMacro implements IMacro

{

 

    public boolean checkIfApplicable(String tagName)

    {

        if (tagName.equals("t:field")) return true;

        if (tagName.equals("t:combofield")) return true;

        return false;

    }

 

    public boolean checkIfAttributeIsAffected(String attribute)

    {

        if (attribute.equals("text")) return true;

        if (attribute.equals("bgpaint")) return true;

        if (attribute.equals("enabled")) return true;

        return false;

    }

 

    public void executeMacro(BaseComponentTag tag, String[] macroParams)

    {

        String property = macroParams[0];

        String property = macroParams[0];

        if (tag.getAttributeMap().get("text") == null)

            tag.setText("#{bean."+property+"}");

        if (tag.getAttributeMap().get("bgpaint") == null)

            tag.setBgpaint("#{bean."+property+"BGPAINT}");

        if (tag.getAttributeMap().get("enabled") == null)

            tag.setEnabled("#{bean."+property+"ENABLED}");    }

 

    public String getName()

    {

        return "propbyclass";

    }

 

    public String[] getMacroParamNames()

    {

        return new String[] {"property"};

    }

}

 

The macro currently does exactly the same as the XML macro definition that was shown within the previous chapter. But, of course: now you could extend the macro's logic by any kind of Java programming.

From the example code you see that the macro only transfers values into the component's attributes if they are null. This means: in case the user explicitly defines component attributes then the macro will not override these definitions.

The macro is used both at runtime (this is when the executeMacro() is called) – and at design time: the Layout Editor needs to know which components and attributes of a component are affected. As consequence: keep the macro's internal Java coding as simple as possible, so that it can be instanciated without problems by the Layout Editor environment.

The class needs to be registered in the configuration file “/eclntjsfserver/config/macros/javamacros.xml”:

<javamacros>

    <javamacro classname="demomacros.DemoMacro"/>

</javamacros>

 

Pay Attention when processing Grid Cells

When implementing your own macro by writing your own class for interface “IMacro”, then there is one special aspect that you have to pay attention to.

A grid definition is typically done int the following way:

FIXGRID objectbinding=”#{d.TestUI.grid}”

  GRIDCOL text=”...”

    FIELD text=”.{firstName]”

 

The property within the grid cell definition is typically not written with an absolute expression but with a relative expression (here: .{firstName}). At runtime the grid processing creates several instances of the cell component (here: FIELD) and assigns a correct, full expression to each cell instance.

Example: if the grid above creates its first line of controls the FIELD's text will get assigned the following expression: “#{d.TestUI.grid.rows[0].firstName}”.

Please be aware of the fact that the macro is called first for the original component (FIELD text=”.{firstName}”) and then for the generated components (FIELD text=”#{d.TestUI.grid.rows[0].firstName}”, ...). Changes that you might apply via macro when processing the “.{...}” expression will automatically be applied to the per-line-components.

In some special situations (e.g. when modifying a component's attribute) you need to pay attention to this, in order to avaoid a double-processing of the same rules you apply via Macro. In this case just check if an expression starts with “.{“ and only apply your rules when an expression starts with “#{“.

Macro Usage

A macro is used in a component by defining the attribute ATTRIBUTEMACRO. Let's use the use the macro “prop.xml” of the previous chapter and apply it to the example use case:

<field id=”g_57”

       attributemacro=”prop(zipCode)”

       width=”200”/>

 

That's it! The field will look and behave exactly the same as in the “long definition” in the chapter above.

And: you may update the macro any time – e.g. you may add a new attribute that is generated (e.g. “zipCodeFOREGROUND”). You do not need to re-define the JSP pages using the macro – it is automatically applied. Of course you must not change the macro's structure in an incompatible way – e.g. removing a macro parameter or changing the order of parameters.

Some more Details

Overriding a Macro Value

You may any time override a macro by an explicit attribute definition. Example:

<field id=”g_57”

       attributemacro=”prop(zipCode)”

       enabled=”false”

       width=”200”/>

 

The ENABLED-definition of the component will always be stronger than the macro. A macro will only set a component's attribute value if it is not explicitly defined.

(Please note: in case of Java class based macros the implementor of the macro has to guarantee that no already defined values are overwritten!)

Tolerant Attribute References

When applying a macro at runtime the macro management will only match these macro attribute definitions that can be applied to the UI component.

Example: the macro “prop.xml” from above should also be used for FORMATTEDFIELD components. These components do not hold their content in the TEXT attribute but in the VALUE attribute. As consequence the macro is extended in the following way:

<macro>

    <applysto>

        <tag name="t:field"/>

        <tag name="t:combofield"/>

        <tag name="t:formattedfield"/>

    </applysto>

    <parameters>

        <parameter name="property"/>

    </parameters>

    <attributes>

        <attribute name="text" value="#{bean.${property}}"/>

        <attribute name="value" value="#{bean.${property}}"/>

        <attribute name="bgpaint" value="#{bean.${property}BGPAINT}"/>

        <attribute name="enabled" value="#{bean.${property}ENABLED}"/>

    </attributes>

</macro>

 

Macros in the Layout Editor

Within the Layout Editor there is a “attributemacro” field in which you can define the macro:

Only these macros will show up in the value help that can be applied to the selected component.

Once defining a macro then all attributes that are set by the macro will be marked:

The REFERENCE Attribute

The one way of passing parameters into a macro definition is to list the parameters in the macro call. Example: “dprop(artikelStamm,sperr_knz)”.

The second way is to use the REFERENCE attribute that is available with each component. Within the reference attribute you can pass a list of “name:value” pairs, separated by semicolon. This list can be accessed both from the XML Macro definition and from the implemented Macro definition.

Example: in a page there is the following definition:

<t:field id="g_48" attributemacro="crud.DetailField()" reference="b:address;p:town" />

 

The reference contains a “b”-value and a “p”-value (“b” for bean, “p” for property). Of course, the naming is completely up to you, “b” and “p” are just examples.

Now you can access these values inside your XML macro definition:

<macro>

    <applysto>

        <tag name="t:field"/>

        <tag name="t:formattedfield"/>

        <tag name="t:calendarfield"/>

        <tag name="t:textarea"/>

        <tag name="t:combobox"/>

        <tag name="t:radiobutton"/>

    </applysto>

    <parameters>

    </parameters>

    <attributes>

        <attribute name="bgpaint" value="#{d.${ref.b}.selBgpaints.${ref.p}}"/>

        <attribute name="text" value="#{d.${ref.b}.sel.${ref.p}}"/>

        <attribute name="value" value="#{d.${ref.b}.sel.${ref.p}}"/>

        <attribute name="width" value="100%"/>    

    </attributes>

</macro>

 

The value of “b” is accessed with “${ref.b}” and the value of “p” with “${ref.p}”. The macro itself does not provide any parameters – all macro input information is taken from the REFERENCE attribute.

The implementation version of the macro would contain the following code:

    public void executeMacro(BaseComponentTag tag, String[] macroParams)

    {

        String reference = tag.getAttributeMap().get(“reference”);

        if (reference == null)

            return;

        Map<String,String> m = ValueManager.decodeComplexValue(reference);

        String bValue = m.get(“b”);

        String pValue = m.get(“p”);

        ...

        ...

    }

Macros within the Grid Processing (FIXGRID)

When writing Java-based macros then you need to be aware of how the grid processing (FIXGRID component) internally works.

A gidi definition contains column definitions:

FIXGRID sbvisibleamount=”3”

  GRIDCOL

    FIELD/...   <== cell component, FIELD as example

  GRIDCOL

    FIELD/...   <== cell component, FIELD as example

 

The grid interprets this structure when it is accessed first time and internally multiplies out this structure:

FIXGRID

  GRIDROW

    GRIDHEADERLABEL <== derived from first GRIDLCOL

    GRIDHEADERLABEL <== derived from second GRIDCOL

  GRIDROW

    FIELD

    FIELD

  GRIDROW

    FIELD

    FIELD

  GRIDROW

    FIELD

    FIELD

 

From macro processing point of view there are two situations when macros are applied:

It's now your choice when to apply your macro's functions: either with the JSP processing or with the FIXGRID processing or with both.

To find out BaseComponentTag provides a special method:

BaseComponentTag.isGridCellComponent()

 

This method returns “false” in the JSP processing phase, and returns “true” if the current component tag is a “multiplied-out-one”.

By the way: what's the main difference, when the components below GRIDCOL are multiplied out? - Answer: the expressions... - An expression “.{xxx}” is transferred into “#{yyy.rows[index].xxx}” by the grid management.

“Default Macros” - Macros that are always applied

There are situations in which you want to always apply some “automated attribute value generation” as part of the component processing.

Example: all graphical components provide an attribute CLIENTNAME – which is a stable name that can identified by e.g. testing tools. You may of course individually define these names within the layout definition – but in many cases you have enough information inside the component to automatically derive some useful value.

So if there is a field with the following definition...

<t:field ... text=”#{d.XyzUI.firstName}” ... />

 

...then you may automatically set the CLIENTNAME to:

<t:field ... text=”#{d.XyzUI.firstName}” clientname=”firstName” ... />

 

(Similar examples could be desribed with other attributes like e.g. HELPID.)

For this reason you can define a Java-based macro class that implements interface “IDefaultMacro” (which is an extension of “IMacro”). The class needs to be registered in the configuration file “/eclntjsfserver/config/macros/javamacros.xml” - just as with “normal” macros.

Example – Automated generation of CLIENTNAME values

The following macro generates CLIENTNAME values out of existing attribute definitions of a component:

package demomacros;

 

import java.util.HashSet;

import java.util.Set;

 

import org.eclnt.jsfserver.elements.BaseComponentTag;

import org.eclnt.jsfserver.elements.ICCComponentProperties;

import org.eclnt.jsfserver.elements.macros.IDefaultMacro;

 

/**

* Macro that is called with any component without being explicitly assigned.

* <br><br>

* The macro fills the CLIENTNAME field with some default value. The default value

* is derived out of the expression of certain attributes.

*/

public class AlwaysMacro

    implements IDefaultMacro, ICCComponentProperties

{

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

 

    static String[] s_refAttributeNamesDefault = new String[]

    {

        ATT_text,

        ATT_value,

        ATT_selected,

        ATT_valuereference,

        ATT_OBJECTBINDING,

        ATT_ACTIONLISTENER

    };

    

    static String[] s_refAttributeNamesInvokers = new String[]

    {

        ATT_ACTIONLISTENER,

        ATT_value,

        ATT_text,

    };

    

    static String[] s_invokers = new String[]

    {

        "t:button",

        "t:icon",

        "t:menuitem",

        "t:link"

    };

    

    static Set<String> s_invokersAsSet; // same as s_invokers, filled in static-initializer

    

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

 

    static

    {

        s_invokersAsSet = new HashSet<String>();

        for (String invoker: s_invokers)

            s_invokersAsSet.add(invoker);

    }

    

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

 

    @Override

    public void executeMacro(BaseComponentTag tag, String[] macroParams)

    {

        String clientname = tag.getAttributeFromAttributeMap(ATT_clientname);

        if (clientname == null)

        {

            String ref = findReference(tag);

            if (ref != null)

            {

                if (clientname == null)

                    tag.setAttributeInAttributeMap(ATT_clientname,ref);

            }

        }

    }

    

    @Override

    public boolean checkIfApplicable(String tagName) { return true; }

    @Override

    public boolean checkIfAttributeIsAffected(String attribute) { return false; }

    @Override

    public String getName() { return "always"; }

    @Override

    public String[] getMacroParamNames() { return null; }

 

    // ------------------------------------------------------------------------

    // inner usage

    // ------------------------------------------------------------------------

 

    /**

     * Find any "stable" reference in the current tag.

     */

    private String findReference(BaseComponentTag tag)

    {

        if (s_invokersAsSet.contains(tag.getTagNameWithPrefix()))

        {

            return findReferenceInAttributeSequence(tag, s_refAttributeNamesInvokers);

        }

        else if ("t:radiobutton".equals(tag.getTagNameWithPrefix()))

        {

            String ref = findReferenceInAttributeSequence(tag,new String[] {ATT_value});

            if (ref != null)

                ref += "_" + tag.getAttributeFromAttributeMap(ATT_refvalue);

            return ref;

        }

        else

        {

            return findReferenceInAttributeSequence(tag, s_refAttributeNamesDefault);

        }

    }

 

    /**

     * Find the "best" reference that can be derived from component by checking for an

     * expression within the attributes that are passed as sequence-parameter. If an

     * expression is found then this expression is the base for calculating the reference.

     * The last "word" of the expression is used as reference.

     * <br><br>

     * Example: if the expression is "#{d.XyzUI.firstName}" then the reference that is found

     * is "firstName".

     * <br><br>

     * Please note: this is only an example of deriving information out of the component!

     * You may use any other algorithm on any other attributes, of course!

     */

    private String findReferenceInAttributeSequence(BaseComponentTag tag, String[] sequence)

    {

        for (String refAttributeName: s_refAttributeNamesDefault)

        {

            String s = tag.getAttributeFromAttributeMap(refAttributeName);

            if (s != null)

            {

                // check if expression

                if ((s.startsWith("#{d.") || s.startsWith(".{")) && s.endsWith("}"))

                {

                    // return last part of expression

                    int index = s.lastIndexOf('.');

                    if (index <= 0)

                        index = 1;

                    String result = s.substring(index+1,s.length()-1);

                    // if expression is kept in array way e.g. "#[d.XyzUI.data['abc']}:

                    // remove certain characters

                    result = result.replace("\'","");

                    result = result.replace("]","");

                    result = result.replace("[","_");

                    return result;

                }

            }

        }

        return null;

    }

 

}

 

 

The macro class needs to be registered in javamacros.xml:

<javamacros>

    ...

    <javamacro classname="demomacros.AlwaysMacro"/>

    ...

</javamacros>

 

Default macros do not need to be referenced by control definitions!

To run the macro you do not have to reference the macro from the component using the ATTRIBUTEMACRO attribute. The macro is automatically applied to all components for which the macro method “checkIfApplicable(String tagName)” returns a true value.

Related Topics

Page Bean Components

Page Beans are a comfortable and simple way of encapsulating a screen as object and re-use it throughout various parts of your application. (Please read details in the chapter “Page Navigation”.)

A page bean consists out of...:

Through being an excellent modularization technology within one project, page beans are not really distributable throughout various projects in a simple way. Imagine you want to use one page bean implementation, that you made in one project, in another project as well. In this case you have to copy various issues from one project to the other:

Basic Idea – One self-containing JAR File

So the concept behind Page Bean Components is:

By adding the JAR file to your project (e.g. as JAR within WEB-INF/lib) you can use the page bean component – and do not have to think about cross-copying additional files in addition.

Example

In the following text we will explain the Page Bean Component concept by using an example – the “multi text” component.



The component displays/edits a list of text-strings. Each time the user adds some new text and presses “Add”, the new text is added to the list of texts on top of the input text area.

The component is part of the demo workplace (demos-project). Is is contained in package “democontrols”. The class name of the component is “DemoPBCMultiText”.

Using a Page Bean Component

Component PAGEBEANCOMPONENT

There is a component PAGEBEANCOMPONENT that allows to use a page bean component:

<t:row id="g_2">

    <t:pagebeancomponent id="g_3"

        pagebeanbinding="#{d.DemoPageBeanComponent.multiText}"

        pagebeanclass="democontrols.DemoPBCMultiText"

        pagebeaninitdata="height:300;width:300" />

</t:row>

 

There are three important attributes:

The server side code for integrating the page bean component inside your class is:

package workplace;

 

...

 

public class DemoPageBeanComponent

    extends PageBean

{

 

    DemoPBCMultiText m_multiText = new DemoPBCMultiText();

    

    public DemoPageBeanComponent(IWorkpageDispatcher workpageDispatcher)

    {

        super(workpageDispatcher);

 

        // filling page bean with data + passing listener

        List<String> texts = new ArrayList<String>();

        texts.add("This is some existing text which is already avaiable.");

        m_multiText.prepare(texts,new DemoPBCMultiText.IListener()

        {

            public void reactOnUpdate()

            {

            }

        });

    }

 

    ...

 

    public DemoPBCMultiText getMultiText() { return m_multiText; }

 

    ...    

}

 

You see: page bean components are embedded into your page and page processing in the same way as normal page beans.

Developing a Page Bean Component – By Example

Let's implement the “multi text” component as component “DemoPBCMultiText” in package “democontrols”.

The basic Files

The following files are contained in the package “democontrols”:

democontrols

    DemoPBCMultiText.jsp           => The layout

    DemoPBCMultiText.java          => The code

    DemoPBCMultiText.properties    => Literals

    DemoPBCMultiText_de.properties => Literals in German

    DemoPBCMultiText.config        => XML configuration     

 

The XML Layout

The layout of the page bean component is stored – just as normal – in a .jsp/.xml file. The .jsp/.xml file is expected to be located within the Java-resources, in the same package in which the .java code is located. The name of the layout must be the same as the name of the page bean component.

In the example the following XML is used within the file “DemoPBCMultiText.jsp”:

<t:pagebeanroot id="g_1">

    <t:pane id="OUTEST" border="#00000030"

        height="#{d.DemoPBCMultiText.height}" padding="10" rowdistance="5"

        width="#{d.DemoPBCMultiText.width}">

        <t:row id="g_4">

            <t:scrollpane id="SCROLLPANE" height="100%" width="100%">

                <t:rowdynamiccontent id="g_6"

                    contentbinding="#{d.DemoPBCMultiText.dynContent}" />

            </t:scrollpane>

        </t:row>

        <t:rowline id="g_7" />

        <t:row id="g_8">

            <t:textarea id="TEXTAREA"

                bgpaint="#{d.DemoPBCMultiText.newTextBgpaint}" height="80"

                requestfocus="#{d.DemoPBCMultiText.newTextRequestFocus}"

                text="#{d.DemoPBCMultiText.newText}" width="100%;100" />

        </t:row>

        <t:row id="g_10">

            <t:coldistance id="g_11" width="100%" />

            <t:button id="ADDBUTTON"

                actionListener="#{d.DemoPBCMultiText.onAddAction}"

                text="#{d.DemoPBCMultiText.lit.btnAdd}" width="100+" />

        </t:row>

    </t:pane>

</t:pagebeanroot>

 

You see: just normal “layout XML”... Please note:

The Java Code

The “DemuPBCMultitext.java” file contains the code of the page bean component:

package democontrols;

 

import java.util.ArrayList;

import java.util.List;

import java.util.Map;

 

import javax.faces.event.ActionEvent;

 

import org.eclnt.jsfserver.elements.componentnodes.PANENode;

import org.eclnt.jsfserver.elements.componentnodes.ROWNode;

import org.eclnt.jsfserver.elements.componentnodes.TEXTPANENode;

import org.eclnt.jsfserver.elements.impl.ROWDYNAMICCONTENTBinding;

import org.eclnt.jsfserver.pagebean.component.PageBeanComponent;

import org.eclnt.jsfserver.session.RequestFocusManager;

 

public class DemoPBCMultiText extends PageBeanComponent

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

    

    public interface IListener

    {

        public void reactOnUpdate();

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

 

    String m_width = "200";

    String m_height = null;

    

    String m_newText = null;

    List<String> m_texts = new ArrayList<String>();

    

    IListener m_listener;

    ROWDYNAMICCONTENTBinding m_dynContent = new ROWDYNAMICCONTENTBinding();

    boolean m_error = false;

    long m_newTextRequestFocus;

    

    // ------------------------------------------------------------------------

    // constructors

    // ------------------------------------------------------------------------

    

    @Override

    public String getRootExpressionUsedInPage() { return "#{d.DemoPBCMultiText}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    public String getNewText() { return m_newText; }

    public void setNewText(String newText) { m_newText = newText; }

    

    public List<String> getTexts() { return m_texts; }

 

    public void setWidth(String width) { m_width = width; }

    public String getWidth() { return m_width; }

    public void setHeight(String height) { m_height = height; }

    public String getHeight() { return m_height; }

    

    public ROWDYNAMICCONTENTBinding getDynContent() { return m_dynContent; }

 

    @Override

    public void initializePageBean(Map<String, String> initData)

    {

        super.initializePageBean(initData);

        {

            String s = initData.get("width");

            if (s != null) setWidth(s);

        }

        {

            String s = initData.get("height");

            if (s != null) setHeight(s);

        }

        render();

    }

 

    public void prepare(List<String> texts, IListener listener)

    {

        m_texts = texts;

        m_newText = null;

        m_listener = listener;

        render();

    }

    

    public String getNewTextBgpaint()

    {

        String result = "write_empty(10,50%,"+getLit().get("phText")+",10,#c0c0c0,leftmiddle)";

        if (m_error) result += ";error()";

        return result;

    }

    

    public long getNewTextRequestFocus() { return m_newTextRequestFocus; }

 

    public void onAddAction(ActionEvent event)

    {

        m_error = false;

        if (m_newText == null || m_newText.trim().length() == 0)

        {

            m_error = true;

            m_newTextRequestFocus = RequestFocusManager.getNewRequestFocusCounter();

            return;

        }

        m_texts.add(m_newText);

        m_newText = null;

        m_newTextRequestFocus = RequestFocusManager.getNewRequestFocusCounter();

        render();

        if (m_listener != null)

            m_listener.reactOnUpdate();

    }

    

    // ------------------------------------------------------------------------

    // private usage

    // ------------------------------------------------------------------------

    

    private void render()

    {

        PANENode p = new PANENode().setWidth("100%").setHeight("100%").setRowdistance(5);

        int counter = -1;

        for (String text: m_texts)

        {

            counter++;

            ROWNode r = new ROWNode();

            p.addSubNode(r);

            TEXTPANENode t = new TEXTPANENode().setWidth("100%").setText(text);

            if (counter%2 == 1)

                t.setBackground("#00000010");

            r.addSubNode(t);

        }

        m_dynContent.setContentNode(p);

    }

    

}

 

Again you see: a just normal page bean implementation... - with some special remarks:

The Property Files

Literals are kept in property files. The page bean component author decides which languages to support. In the example there are two files:

File: DemoPBCMultiText.properties

 

btnAdd = Add

phText = Please enter some text...

 

 

File: DemoPBCMultiText_de.properties

 

btnAdd = Hinzufügen

phText = Bitte geben Sie einen Text ein...

 

The Configuration File

The configuration file “DemoPBCMultiText.config” passes information into the Layout Editor environment to support the user of a page bean component when embedding the component into a page. The file is NOT used operationally at runtime at the moment.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<pageBeanConfig>

    <columnComponent>true</columnComponent>

    <param>

        <name>width</name>

    </param>

    <param>

        <name>height</name>

    </param>

</pageBeanConfig>

 

There are the following definitions:

Extending style definitions

When developing components then these components typically come with some own style definitions. These style definitions need to be located into some Java package as well. The name of the package is yours, you just have to provide the name later on when providing the meta information for the components. (See next chapter.)

Let's assume you select the package “democontrols.styleextensions”.

Inside the package you may now extend the existing styles of CaptainCasa or the ones that you define your own by following the same directory concept that your are used from normal CaptainCasa style definitions:

Example: you want to add own style definitions to the “defaultrisc” style. In this case the file structures looks as follows:

democontrols

    styleextensions

        defaultrisc

            riscstyle.xml <== CSS styling

            style.xml     <== CaptainCasa attribute styling

 

Please pay attention: while the name “style.xml” is the same for all styles, the name for the CSS styling varies from style to style! You need to make sure that the same file name is used within your style extension than the one that is used in the original style.

At runtime the style definitions coming from the original styles and the style definitions coming from your extension are concatenated to form one XML which then is used within the style processing. As consequence please make sure:

Providing Meta Information

When packaging one or several page bean components into one .jar file, then you may add certain meta information. This meta information is user by the CaptainCasa tooling – so that the Page Bean Components that come with a certain .jar file are directly integrated into the layout editor.

The configuration is simple: just add a file “ccpagebeancomponentinfo.xml” to the source/resource part of your project and list the Page Bean Component classes, that are part of the .jar file:

CaptainCasa project layout:

 

<yourproject>
   /src

        ccpagebeancomponentinfo.xml

 

Maven project layout:

<yourprojec>

    /src

        /main

            /java

            /resources

                ccpagebeancomponentinfo.xml

            /webapp

 

Content:

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<pageBeanComponents packageOfStyleExtensions=”democontrols.styleextensions”>

    <pageBeanComponent className="democontrols.DemoPBCMultiText"/>

    <pageBeanComponent className="..."/>

    <pageBeanComponent className="...”/>

</pageBeanComponents>

 

You only need to defined the attribute “packageOfStyleExtensions” if you really extended the style.

Providing Meta Information – old way...

This chapter describes the adding of meta information using interface IPageBeanComponentInfoService. This way is a bit more complex than the one presented in the previous chapter, but still is supported. - If you are not using the interface IPageBeanComponentInfoServer yet, then please skip this part of the documentation!

When packaging one or several page bean components into one .jar file, then you may add certain meta information. The meta information is:

The meta information is added by following the Java Service Locator concept, in which some interface is defined and its implementation is registered within the META-INF directory of the .jar file.

You need to create a class, supporting interface “IPageBeanComponentInfoService”. The interface looks like:

package org.eclnt.jsfserver.pagebean.component;

 

...

 

public interface IPageBeanComponentInfoService

{

    public List<Class> getPageBeanComponents();

    public String getPackageNameOfStyleExtensions();

}

 

The implementation of the class in the “eclnt_pbc.jar” package looks like:

public class MyPageBeanInfoService extends

                                   DefaultPageBeanComponentInfoService

{

    List<Class> m_pageBeanClasses = null;

 

    @Override

    public List<Class> getPageBeanComponents()

    {

        if (m_pageBeanClasses == null)

        {

            m_pageBeanClasses = new ArrayList<Class>();

            m_pageBeanClasses.add(democontrols.DemoPBCMultiText.class);

        }

        return m_pageBeanClasses;

    }

 

    @Override

    public String getPackageNameOfStyleExtensions()

    {

        return "democontrols.styleextensions";

    }

}

 

Please note: the class itself inherits from “DefaultPageBeanComponentInfoService” which is the default implementation of the interface. When CaptainCasa extends the interface then the default implementation will always provide some adequate default implementation and you do not have to immediately updated your own implementation.

You need to register your class in the META-INF directory of your .jar file. This is done in the following way:

Create the UTF8-file with the name...

src

  META-INF

    services

      org.eclnt.jsfserver.pagebean.component.IPageBeanComponentInfoService

 

...within the META-INF/services/ directory of your .jar file. Inside this file you just list the implementation class of interface “IpageBeanComponentInfoService”, so the content of the file is:

org.eclnt.ccaddons.pbc.MyPageBeanInfoService

 

Make sure that this META-INF-information is added to the .jar file when packaging.

Packaging and Delivering a Page Bean Component

Basics

Packaging and delivering a page bean component means that you just have to package all artifacts into one JAR file. This JAR files then contains the following files:

democontrols

    DemoPBCMultiText.xml           => The layout

    DemoPBCMultiText.class             => The byte code

    DemoPBCMultiText.properties    => Literals

    DemoPBCMultiText_de.properties => Literals in German

    DemoPBCMultiText.config        => XML configuration     

ccpagebeancomponentinfo.xml

 

Of course you are “allowed” to add further class files in order to split the page bean component's functions into several classes. Of course you may bundle several page bean components into one JAR file.

The user of a page bean just has to add the JAR file to his/her server side processing – typically by adding it to WEB-INF/lib.

Managing Page Bean Components within the Layout Editor

Previous versions: include the package in the dispatcherinfo.xml

If using “ccdispatcherinfo.xml” for registering packages / managed beans then this step is obsolete!

If using “dispatcherinfo.xml” for registering packages / managed beans then you have to explicitly include the packages containing manages bean implementations. This step only needs to be done in the project in which you create the component – it is not required to be done in the projects which take use of the components.

Register the component's package within the Dispatcher-management of the project, most simply done by adding the package name into the “dispatcherinfo.xml” file:

<dispatcherinfo>

    ...

    <managedpackage name="democontrols"/>

    ...

</dispatcherinfo>

Creating the Layout of Page Bean Components

The layout file must be part of your sources (at design time) and part of your classes/jar-libraries (at run time). So create the layouts in the “Source”-area of your project.

Example:

For the creation of page bean components there is a dedicated template:

The template contains the following default layout:

<t:pagebeanroot id="g_ccpreview_1" >

    <t:pane id="g_ccpreview_2" comment="Only one component is allowed below pagebeanroot!" >

        <t:beanprocessing id="g_ccpreview_3" />

        <t:row id="g_ccpreview_4" >

            <t:label id="g_ccpreview_5" text="Some content" />

        </t:row>

    </t:pane>

</t:pagebeanroot>

 

Use the PAGEBEANROOT component as top anchor of your layout definition. Within the PAGEBEANROOT you can arrange exactly one (!) sub-component. In the template this component is proposed to be a PANE, in which you the can arrange any other content.

The PAGEBEANROOT component is a “quite clever” one: it allows to both position the component within a container-row (as column component) or to position the component inside a container (as row component).

Creating the Java-program

When using the Java-program creation of the Layout Editor then create the program in the following way:

Pay attention to the following issues:

Development

You now can develop the component in a just normal way – the same way that you use for developing normal pages.

Information for users of version earlier than 20200827

Storing resources in the Java-classes

The update 20200827 was the one in which resources also can be stored in the classes-area of an application – before it was by default only possible to store resources in the web content part of an application. Before 20200827 page bean components were specially treated by the runtime – and the layout was stored in some “.xml” file next to the code. This special treatment not completely became obsolete due to the improvements in the resources area.

The following information as consequence is obsolete if using a version >= 20200827.

Automatically saving the “.xml”-layout within the Layout Editor

Within the editor you can set up some bridge between editing JSP files and automatically storing them as XML files within the source directory. The advantage: you can use the editor for implementing your page beans “just as normal” and do not have to take care of transferring the layout from the “.jsp/.xml” file (managed by the Layout Editor) and the “.xml” file that holds the layout within your page bean package.

The bridge is quite simple:

The definition is done in the following way. Open the configuration “...PageBean Copy”:

A dialog will show up:

After saving the configuration the Layout Editor will know, that every time it stores a “.jsp/.xml” layout within the corresponding directory(ies) it will also store a copy in the corresponding package of the page bean component.

The information is saved within the CaptainCasa-project file (“.ccproject” within your project root directory):

<project ...>

    ...

    <pagebeancomponentcopy jspdirectory="pagebeanlayouts"

                           packagename="democontrols" >

    </pagebeancomponentcopy>

    ...

</project>

Including Resources (Images, …)

You may add resources like images into the .jar file that you build for the page bean component. E.g. you add an image to your page bean component source:

democontrols

   DemoPBCMultiText.xml           

   ...

   resources

       filter.png

 

So the name of the resource in the package is “democontrols.resources.filter.png”.

For accessing the resource from the client side there is a special servlet-API (“CLResourceAccessServlet”) which is registered inside your web.xml.

You simply have to form an image-URL in the following way:

/democontrols.resources.filter.png.ccclresource

 

Result: the image will be loaded into the corresponding browser processing.

Adapting the Page Bean Component Management to >= 20200827

You may easily adapt the management of page bean components by executing the following steps:

Extending Page Bean Components

In many cases you may want to extend page bean components to adapt to your needs.

Example

There is a page bean component “CCCSVString” in which a semicolon-separated string is shown in some COMBOFIELD which then opens up a popup to edit the values:

The XML layout definition of the page bean component is:

<t:combofield id="COMBOFIELD"

    actionListener="#{d.CCCSVString.onValueHelpAction}" editable="false"

    enabled="#{d.CCCSVString.enabled}" text="#{d.CCCSVString.csvString}"

    width="#{d.CCCSVString.width}" />

 

You see that the component designer did not consider the attribute LABELTEXT – and this is something that you want to add into the existing component.

Java Extension

The first step is to write a Java class extending the original class of the page bean component – and adding there the functions and properties that you require:

package workplace.pbcext;

 

import java.util.Map;

 

import org.eclnt.ccaddons.pbc.CCCSVString;

 

public class MyCSVString extends CCCSVString

{

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

 

    String m_labelText;

    

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

 

    public String getLabelText() { return m_labelText; }

    public void setLabelText(String labelText) { m_labelText = labelText; }

 

    @Override

    public void initializePageBean(Map<String, String> initData)

    {

        super.initializePageBean(initData);

        if (m_labelText == null) m_labelText = initData.get("labeltext");

    }

 

}

 

Now you have some place to define the labelText in the code... But you somehow need to transfer the value of “labelText” into the COMBOFIELD's layout definition.

Layout XML modification

So what you need to do is to update the layout XML definition. You may either do this by completely writing the whole XML on your own and store it in the same package with the name “MyCSVString.xml”.

Or, which is much smarter in many case, you may write a little XML file that describes the layout modifications that you want to do...:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<modification>

    <control id="COMBOFIELD">

        <attribute name="labeltext" value="#{d.CCCSVString.labelText}"/>

    </control>

</modification>

 

This file defines that in the original XML file (coming from CCCSVString.xml) you update the component with id “COMBOFIELD” and you define the attribute LABELTEXT to be bound to the expression pointing the getLabelText()-method. You may update one or several attributes. The values that you define overwrite existing values coming from the original value.

In the XML file you may define:

The value might be an expression-value or a direct value. Example:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<modification>

    <control id="COMBOFIELD">

        <attribute name="labeltext" value="#{d.CCCSVString.labelText}"/>

        <attribute name="tooltip" value="This is a CSV value."/>

    </control>

</modification>

 

Naming Conventions

You need to follow the conventions:

For the example this means:

Class name: workplace.pbcext.MyCSVString
XML name:    workplace.pbcext.MyCSVString.mod.xml

 

Stable component ids required

The base for defining layout modifications is the usage of stable ids within the original component's layout definition – which are also kept stable when the component is updated and modified by the author of the original component.

By default component ids in the CaptainCasa toolset are counted. So if the author of the original component provides ids like “g_##” then there's a high probability that these ids might not be stable with future enhancements of the component.

Talk to the author of the page bean component in case the naming of ids is not properly done...!

File Download & File Upload

There are a couple of components that provide the possibility to upload/download files from/to the user's client. The demo workplace contains a number of examples that demonstrate what you can do – please take implementation details from there.

File Download

The following components all trigger the download of a certain file to the client side.

While FILEDOWNLOADBUTTON and FILEDOWNLOADLINK are visible components, that trigger the download when the user presses the button/link, FILEDOWNLOAD is an invisible component that start the download via a trigger-attribute.

Once triggered a corresponding popup will be opened for the user, so that the user can select/change the location of the file to be downloaded.

The download itself is done through a URL-attribute that is defined as attribute of the component. The URL should point into your web application. The client opens up the URL, reads the bytes by a corresponding http-request and stores the bytes within the defined file on client side.

Static URLs – Static Content

Well, what we tell now sounds very easy (and it is), but it will not meet your real life requirements... Never the less it maybe useful for certain situations.

...

<t:filedownloadbutton url=”/images/car.png” filename=”car.png”/>

...

 

The URL “/images/car.png” point to a static file “car.png” within the “images” directory of your web application. The file name on client side, that will be proposed to the user is “car.png” as well.

You see, it is very easy to download static content that is part of your web application. BUT: you should never use this way of accessing information in order to download dynamic content!

Example: you could define a directory “download” within your web application, and then write some temporary files into the download directory, that you then dynamically pass as URL into the download-component.

Why you should never do this: first of all some applications server (different to Tomcat), do not open up a web-application-directory at all – but store the web application somehow in internal files. As consequence there is no directory to write to.

And: in clustered scenarios requests potencially may hit different machines. So the client, resolving the URL “/download/temp1234” may hit a different machine than the one were you wrote the file to.

In short: writing information into the web application directory is “dirty programming” and that's what you should not do. There are much nicer ways, in addition – please have a look into the next chapter.

Dynamic URLs – Dynamic Content

The solution to provide dynamic content via URL is quite simple: instead of passing back a static URL, a URL is passed back that invokes a certain servlet, that then itself triggers the passing of the dynamic content. A consequence the request that is sent from client side when the download is triggered hits a dynamic program structure rather than a static file system.

In order to simplify this dynamic, servlet based loading of data there are already some structures defined within the server side processing of CaptainCasa Enterprise Client.

There is an interface “IBufferedContent” and a default implementation “DefaultBufferedContent”. The method that you need to define in your implementation is the following:

    class YourBufferedContent extends DefaultBufferedContent

    {

        public byte[] getContent()

        {

            ...

            ...

            ...

            // load the content here

            ...

            ...

            ...

        }

    }

 

When creating an instance of this class you need to register this instance within the so called buffered-content-manager:

        YourBufferedContent bc = new YourBufferedContent();

        BufferedContentMgr.add(bc);

 

As part of the creating of the instance and of registering, the instance has received a unique id and is registered under this id within the session context.

From the instance you can now load a URL – the URL contains all the parameters that are required later on to find the instance from the BufferedContentServlet, shown in the graph above. The instance's URL may for example be:

BUFFERED_4711.ccbuffer;jsessionid=dfk34823544539

 

This URL, that you receive from the instance is “perfectly made” for being passed as URL into the download components.

When the user triggers the download, then the URL is requested from client side – the instance is caught up by accessing the session context and the instance's method “getContent()” is called. The result of “getContent()” is passed back as response to the client side – and stored as local file over there.

Big Dynamic Content Scenarios

When applying the mechanism explained in the previous chapter to scenarios, in which you may want to download megabytes of data, then there is one problem: you have to pass back the content as byte-array. This means, that the whole megabyte-content is kept in memory on server side for a certain duration of time.

As consequence there is a a second interface mechanism, that exactly operates the same way as already described with IBufferedContent in the previous chapter – but now not loading the content as byte-array, but now by letting you pass the content into an output stream.

    class YourBufferedContent extends DefaultBufferedStreamContent

    {

        public void writeStream(OutputStream stream)

        {

            ...

            ...

            ...

            // provide content and write into stream

            ...

            ...

            ...

        }

    }

 

Synchronous – Asynchronous

Each component for downloading information from the server to the client provides an attribute ASYNCHRONOUS. By default the download is done in a synchronous way – i.e. the user has to wait with further processing on client side until the download is finished. By setting ASYNCHRONOUS to “true” you can decouple the downloading from the normal request/response processing of the Enterprise Client.

File Upload

For uploading files there are a couple of components as well:

The components provide a different way of rendering, but all operate the same way, when it comes to uploading client side file information.

There are two different usage modes that can be applied:

Synchronous Upload

This is the default mode. When the user triggers the upload (e.g. by pressing the button of FILEUPLOADBUTTON), then a file selection is popped up. After selecting the file the content will be uploaded as just normal request that is coming from the client.

Consequently the action listener that is associated with the component is called on server side, and the file information (client file name and file content) is passed through the “BaseActionEventUpload”.

Advantage: the processing of the server side is “in sync” with the upload processing: e.g. you might directly process the uploaded content within the follow-on processing.

There are two disadvantages of synchronous upload components that you need to pay attention to, especially when dealing with bigger file sizes (starting at “some megabytes”):

Consequence: use the synchronous upload for “not-too-big” file sizes and if there is a certain sequence of processing on server side that you want to enforce. For files which are bigger than 5MBs please use asynchronous upload components, especially because of memory considerations.

Asynchronous Upload

This mode is invoked when specifying the attribute ASYNCHRONOUSUPLOADURL within the components.

Now the file is uploaded to a URL in parallel to the normal request/response processing of the Enterprise Client. The URL needs to point to a servlet processing that processes the file content on server side.

Very similar to the servlet management that is used for downloading dynamic content (please take a look into this chapter), there is an interface/object structure available that simplifies the server side processing when receiving an upload request:

You either implement the interface IUploadContent (by sub-classing from DefaultUploadContent) or you implement the interface IUploadStreamContent (by sub-classing from DefaultUplaodStreamContent).


The same structure is applied when using interface “IUploadStreamContent”.

    public class MyUploadContent extends DefaultUploadContent

    {

        public void beginPassing()

        {

            m_content = "Upload started.\n";

        }

        public void passClientFile(String fileName, byte[] bytes)

        {

            m_content += "File was passed: client name " +

                          fileName + ", length " + bytes.length +"\n";

        }

        public void endPassing()

        {

            m_content += "Upload ended.";

        }

    }

    

    public class MyUploadStreamContent extends DefaultUploadStreamContent

    {

        public void beginPassing()

        {

            m_content = "Stream upload started.\n";

        }

        public void passClientFilesAsStream(InputStream stream)

        {

            try

            {

                StringBuffer sb = new StringBuffer();

                while (true)

                {

                    int b1 = stream.read();

                    if (b1 < 0) break;

                    char c = (char)b1;

                    sb.append(c);

                }

                m_content += sb.toString()+ "\n";

            }

            catch (Throwable t)

            {

                CLog.L.log(CLog.LL_ERR,

                           "error when receivng file from client",t);

            }

        }

        public void endPassing()

        {

            m_content += "Stream upload ended.";

        }

    }

 

When implementing your own classes to manage upload requests, you need to implement these methods that actually transfer the data from the upload-request into the application:

When creating instances you need to register these instances, so that they are parked within the http session context on server side. The registration is done in the following way:

        // creating instances + register

        MyUploadContent muc = new MyUploadContent();

        UploadContentMgr.add(muc);

        MyUploadStreamContent musc = new MyUploadStreamContent();

        UploadContentMgr.add(musc);

        // assign URL of instances to propery-members that are

        // references by the upload components

        m_uploadURL = muc.getURL();

        m_uploadStreamURL = musc.getURL();

 

The URL that you provide to be used as ASYNCHRONOUSUPLOADURL is taken out of the “getURL()” method of your instances.

Please read the JavaDoc in order to find more details about the usage of the interfaces, e.g. about the format of the stream when using IUploadStreamContent.

Asynchronous Upload – Notification of “Finished”!

After having successfully uploaded a file/ some files the client side upload processing will trigger the action listener that is bound within the corresponding upload component. The event that is passed into the action listener is of class “BaseActionEventUploadAsynchronousFinished”.

Garbage Collection Issues

The registration of “IUploadContent” and “IUploadStreamContent” objects into the “UploadContentMgr” means that a central session instance now keeps pointers to the instances that you pass.

You need to careful think about when to un-register your instances – otherwise your server side memory will not garbage collect your objects. Un-registering is done by calling the “UploadContentMgr.remove(...)”-method.

There are two techniques how to un-register:

        m_uploadContent = new MyUploadContent();

        UploadContentMgr.add(m_uploadContent);

        m_uploadStreamContent = new MyUploadStreamContent();

        UploadContentMgr.add(m_uploadStreamContent);

        getWorkpage().addLifecycleListener(new

        WorkpageDefaultLifecycleListener()

        {

            public void reactOnDestroyed()

            {

                super.reactOnDestroyed();

                UploadContentMgr.remove(m_uploadContent);

                UploadContentMgr.remove(m_uploadStreamContent);

            }

        });

 

    IUploadContent m_uploadContent;

 

    public class RemoveUploadContent implements Runnable

    {

        public void run()

        {

            if (m_uploadContent != null)

            {

                UploadContentMgr.remove(m_uploadContent);

                m_uploadContent = null;

            }

        }

    }

 

    public String getUploadContentURL()

    {

        if (m_uploadContent == null)

        {

            m_uploadContent = new ...;

            UploadContentMgr.add(m_uploadContent);

            PhaseManager.runBeforeUpdatePhase(new RemoveUploadContent());

        }

        return m_uploadContent.getURL();

    }      

 

Result: the registration of an object is only kept for a certain point of time (until next request is processed).

Configuration Issues

When upload client side information, then you will typically exceed limits that are defined for “normal request processing”. Most typical, there is a “max request size” definition somewhere in your application server configuration settings...

Tomcat

You need to configure the connector in tomcat/conf/server.xml to allow post-request-sizes greater than 1 MB.

    <Connector port="50000" protocol="HTTP/1.1"

               connectionTimeout="20000"

               redirectPort="50001"

               maxPostSize="-1"/>

 

By defining a maxPostSize of “-1” any request sizes are allowed to be uploaded.

Please read further details within the documentation available with Tomcat.

Touch Input

Components

CaptainCasa provides a couple of components that allow to key in text using virtual keyboards:





Definition of own Keyboard Layouts

You may define own keyboard layouts that are referenced by the corresponding component definition.

Example:



The field definition is:

<t:field id="g_11" touchlayout="owndef" touchsupport="true"

    width="100" />

 

The attribute TOUCHLAYOUT is defined as “owndef”, which is the pointer to the server side definition of a keyboard layout.

Definition of keyboard layouts

On server side the definition is kept in a configuration file “touchlayouts.xml”, which itself is part of the directory “/eclntjsfserver/config”. The file's content is defined in the following way:

<touchlayouts>

  ...

  ...

    <layout name="owndef"

          line0="t;h;i;s"

          line1="i;s;@null@;a"

           line2="s;t;r;a;n;g;e"

          line3="l;a;y;o;u;t;DEL/1/@clearlast@;C/1/@clearall@" />

    <layout name="helloworld"

          line0="H;e;l;l;o"

          line1="w;o;r;l;d"

           line2="DEL/1/@clearlast@;C/1/@clearall@" />

  ...

  ...

</touchlayouts>

 

For each layout there is one corresponsing “<layout … />” section. In the section there are several lines being numbered line0, line1, line2 etc.

Each line itself contains the key-definition for one row of the layout. The format is: “<def1>;<def2>;<def3>;...” - each definition representing one key.

A key definition may be defined in two ways:

There are a couple of special characters that you may use as <char>-definition:

There is the possibility to navigate between different keyboard layouts. Just define the key “@layout:<layoutname>@” as character.

 

Server-side Events, long Server-side Operations

Within the Demo Workplace (“General Issues” > “Server Event Processing”) there are couple of examples for each aspect that is part of this chapter. Please look there for details for implementation.

Introduction

Default: Request-Response driven Communication

The default way the CaptainCasa client talks to the server side processing is:


During the phase when the client talks to the server the client itself is blocked for input, in order to avoid inconsistency of data.

The strict request-response driven way of communication is the best type of communication for the default input/output while the user is working with a screen:

Sending interim status information to client

Since 20201221 it is possible to send interim information about the progress of the server side processing:

public void onLongAction(javax.faces.event.ActionEvent event)

{

    try

    {

        for (int i=0; i<100; i++)

        {

            // ...

            // do something

            // ...

            BlockerInfo.sendProgressToClient("Processing step " + (i+1),i+1);

        }

    }

    catch (Throwable t)

    {

        Statusbar.outputError(t.toString());

    }

}

 

The interim progress information is passed by calling the method BlockerInfo.sendProgressToClient(). There are two parameters:

On client side the progress information is added below the busy indicator that is shown when a dialog is waiting for the response of the server:

Internally the client opens up a web socket connection to the server side, which is used to transfer the progress information. This web socket connection is independent from the normal request/response based connection that is processed by normal user activities.

Requirement for enhanced Communication

But of course there are certain limitations as well:

Result: there is a need for enhanced, “event-driven” communication and processing for certain, dedicated scenarios – in parallel to the normal request-response driven way of processing.

Pay Attention – Thread-Complexity, JEE Rules...

In this chapter you will see, how certain processing on server side will be shifted into threads on their own, running in parallel to the normal request-response thread. Looking on pure JEE specifications you are not allowed to open threads on server-side on your own. Of course many JEE implementations (e.g. Tomcat) do not have problems, but some have.

So, in case you want to stick 100% to JEE rules you need to re-think how to decouple processing on server side - e.g. one way might be the usage of JMS...

In addition: working with threads etc. adds a certain additional complexity into your server side development. You need to be aware about this and you need to put extreme care into your implementation! - Finally you need to make sure that your network configuration and cluster configuration supports what you want to do.

Constant Polling – The TIMER way

If you only want to have strict request-response driven communication then this is the only way: you set up a TIMER component within the client that regularly polls information “every xx seconds”. It's like a button being pressed regularly by the user.

The TIMER component provides two significant attributes:

You can update the duration by binding it to a property – if you set value “0” then the TIMER component will stop sending requests.

As result you can build scenarios like the following:


The first request from the client starts a processing thread on the server side – and returns back to the client – so that the user interface is “freed up” quite fast. A timer will afterwards regularly send requests to check the status of the processing thread. When finished, the timer may de-activate itself.

Of course there are some disadvantages with polling:

As result we recommend timer based polling for scenarios in which you want to reguluarly but not very often (e.g. every 2 minutes) check for things happening on server side.

Long Polling – True, synchronous Event Coupling

The principles behind long polling are:

There are two technical ways to establish the event connection between client and server:

From chronological point of view the long polling via traditional http-request/response was available first – and the web socket communication was added later on.

Today “all” networks are capable to handle web sockets, so you should go the web-socket way by default! It is much more lightweight both on client and on server-side. And it does not have as many restrictions as the traditional http-way. Example: even today (July 2018) a browser has a limited number of parallel connections that can be used to communicate to one server. A Chrome browser only allows 6 http connections to be opened against one server. This means: when having opened 6 long polling connections (e.g. by 6 browser tabs) then there is no communication channel left – and the corresponding pages will freeze from communication point of view.

Conclusion: by default we recommend to use the web socket way of communicating events from the server to the client.

Traditional Long Polling – Keeping one connection open

In CaptainCasa the traditional long polling is implemented in the following way:


On client side a special request from a long polling component (LONGPOLLING) is sent to the server. When being woken up the response is communicated back. As consequence the client side triggers a real, normal request-response communication. - What is not shown in the picture: after having processed the response, the long polling directly starts a new request to the server side, waiting for the next event.

You can think of the LONGPOLLING component as virtual button, that is pressed by receiving a response from the server side.

OK, so there are two levels of communication:

The event channel now requires a URL to go to, behind this URL there needs to be the possibility to add some Java code. So for this purpose CaptainCasa introduces a concept that is very similar to the concept of providing URLs for dynamic down/upload of data:


Within the HttpSession context an instance of “DefaultLongPolling” (or sub-class) is registered with a certain id. The instance passes back a URL, containing this id. The client is calling the URL, the central servlet dispatches the request to the LongPolling instance.

Have a look into the following codes:

package workplace;

 

public class DemoLongPolling

    extends DemoBase

    implements Serializable

{

    // -----------------------------------------------------------------

    // inner classes

    // -----------------------------------------------------------------

 

    class EventCreatorThread extends Thread

    {

        boolean i_continue = true;

        public void run()

        {

            while (i_continue == true)

            {

                try

                {

                    Thread.currentThread().sleep(5000);

                    if (i_continue == true)

                        m_longPolling.wakeup(true);

                    else

                        break;

                }

                catch (Throwable t)

                {

                    CLog.L.log(CLog.LL_ERR,"Error occurred when waking up thread",t);

                    break;

                }

            }

        }

    }

    

    // -----------------------------------------------------------------

    // members

    // -----------------------------------------------------------------

 

    DefaultLongPolling m_longPolling = new DefaultLongPolling();

    EventCreatorThread m_eventCreatorThread = new EventCreatorThread();

    

    String m_message = new String();

    

    // -----------------------------------------------------------------

    // constructors

    // -----------------------------------------------------------------

    

    public DemoLongPolling(IWorkpageDispatcher dispatcher)

    {

        super(dispatcher);

        LongPollingMgr.add(m_longPolling);

        m_eventCreatorThread.start();

        getWorkpage().addLifecycleListener(new WorkpageDefaultLifecycleListener()

        {

            @Override

            public void reactOnDestroyed()

            {

                super.reactOnDestroyed();

                CLog.L.log(CLog.LL_INF,"Cleaning up threads...");

                LongPollingMgr.remove(m_longPolling);

                m_eventCreatorThread.i_continue = false;

            }

        });

    }

    

    // -----------------------------------------------------------------

    // public usage

    // -----------------------------------------------------------------

 

    public String getLongPollingURL() { return m_longPolling.getURL(); }

    

    public void onLongPollingAction(ActionEvent event)

    {

        m_message = "Call to server...! " + System.currentTimeMillis() + "\n" + m_message;

    }

    

    public String getMessage() { return m_message; }

    

}

 

An instance of DefaultLongPolling is created and registered.

Whenever a long polling request is started, then this waits within the DefaultLongPolling instance, until the method “wakeup(true/false)” is called. By passing back “true” you define that long polling will continue, i.e. the client will send the next long polling request immediately after having processed the response of the current one.

For a better understanding, what's going on internally, we show you some code of DefaultLongPolling:

public boolean waitForEvent()

{

    ...

    ...

        synchronized(this)

        {

            try

            {

                CLog.L.log(CLog.LL_INF,"Now waiting for event to wakeup this long polling thread");

                m_justWaiting = true;

                this.wait();

                CLog.L.log(CLog.LL_INF,"Event woke up this thread");

                m_justWaiting = false;

                return m_continuePolling;

            }

            catch (Throwable t)

            {

                CLog.L.log(CLog.LL_ERR,"Error occurred when falling to sleep.",t);

                throw new Error(t);

            }

        }

    ...

    ...

}

 

public void wakeup(boolean continuePolling)

{

    ...

    ...

    {

        ...

        synchronized(this)

        {

            try

            {

                CLog.L.log(CLog.LL_INF,"Wakeup was called for the long polling thread, continuePolling = " + continuePolling);

                m_continuePolling = continuePolling;

                this.notify();

            }

            catch (Throwable t)

            {

                m_continuePolling = false;

                CLog.L.log(CLog.LL_ERR,"Error occurred when falling to sleep.");

                throw new Error(t);

            }

        }

    }

    ...

}

 

You see that the thread waits within the waitForEvent method (which is called by the LongPollingServlet) until someone calls the wakeup method.

Finally have a look onto the JSP definition:

<t:beanprocessing id="g_1">

    <t:longpolling id="g_2"

        actionListener="#{d.DemoLongPolling.onLongPollingAction}"

            longpollingurl="#{d.DemoLongPolling.longPollingURL}" />

</t:beanprocessing>

<t:rowdemobodypane id="g_3" objectbinding="#{d.DemoLongPolling}">

    <t:row id="g_4">

        <t:textarea id="g_5" enabled="false" height="100%"

                    text="#{d.DemoLongPolling.message}"                         width="100%" />

    </t:row>

</t:rowdemobodypane>

 

You see the LONGPOLLING component's main configuration consists of:

Please note

In the meantime there is a long polling available that internally works the same principal way as the “traditional” long polling just described – but which now uses web-sockets for communicating events from the server to the client. Because web-socket connections are much more lightweight than traditional http-connections, we strongly recommend to use web-socket based long polling.

Traditional Long Polling – The “Comet-Way”

(This way is only to be used in special cases – the default way is web-socket based long polling.)

The server side part of long polling that was described in the previous chapter is based on the normal JEE-servlet processing:

From client side point of view the long polling processing is the same as waiting for a very slow response. From server side point of view per long polling a thread is blocked until a certain event releases the thread to respond to the client.

Consequence on server side: there is a potentially high number of threads that is to be managed in the server. If each client for some reason opens up one long polling synchronization – then there is always one thread per client on server side to be managed. This is fine for a low number of clients – but it is a load problem if you want to use long polling for e.g. hundreds of clients.

Luckily there is an alternative processing on server side. This is based on the so called “Comet” protocol, which is a special handling of http-requests on server side. It internally is based on Java NIO functions (non blocking input/output).

Advantage: there is no blocking of threads anymore. Threads are only “blocked” when the request enters the system and when the response is written to the client – but threads are NOT blocked when waiting for an event on server side.

As consequence you can in principle manage many more parallel long polling connections, so connecting hundreds of clients is no load problem (at least not from threading point of view).

Using the Comet Way

From API point of view there is nearly no difference at all when switching to the Comet way. Instead of creating an instance of “DefaultLongPolling” you need to pass an instance of “DefaultLongPollingComet” - which has exactly the same interface.

“DeafultLongPollingComet” and “DefaultLongPolling” both share the interface “ILongPolling”, so there is no difference at all from usage point of view.

There are some configuration issues that you need to apply:

  <!--

      Optional, only Tomcat, comet based LongPollingServlet

      <servlet id="LongPollingServletComet">

        <servlet-name>LongPollingServletComet</servlet-name>

        <servlet-class>org.eclnt.jsfserver.polling.comet.LongPollingServletComet</servlet-class>

        <load-on-startup>1</load-on-startup>

      </servlet>

...

...

  <!--

      Optional, only Tomcat, comet based LongPollingServlet

      <servlet-mapping>

        <servlet-name>LongPollingServletComet</servlet-name>

        <url-pattern>*.cclongpollingcomet</url-pattern>

      </servlet-mapping>

  →

 

    <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"

               connectionTimeout="20000"

               redirectPort="8443"

               maxPostSize="1000000"/>               

 

Comet Disadvantage – Only Tomcat...!

You may already have noticed when reading the text above: we talk quite a lot about Tomcat... Well, the reason behind is: the Comet processing is not part of JEE processing, but is dependent from the servlet container that you use.

You only may use the Comet processing within a Tomcat (>= version 6) environment as consequence.

Traditional Long Polling – The “Servlet 3.0” way

(This way is only to be used in special cases – the default way is web-socket based long polling.)

With update 20150504 a third way was introduced how to handle long polling on server side. This way is based on the servlet 3.0 standard features in the area of asynchronous request processing. If using it: make sure that your servlet container supports servlet 3.0!

In the Tomcat environment, servlet 3.0 was introduced with the Tomcat 7 version.

Similar to the comet processing a servlet request that is processed in the server can tell the servlet engine that it is passing the response in an asynchronous way. The waiting for some asynchronous event is then taking place without blocking the request thread.

The following things need to be done in order to use the servlet 3.0 asynchronous processing:

      <servlet id="LongPollingServlet30API">

        <servlet-name>LongPollingServlet30API</servlet-name>

        <servlet-class>org.eclnt.jsfserver.polling.LongPollingServlet30API</servlet-class>

        <load-on-startup>1</load-on-startup>

        <async-supported>true</async-supported>        

      </servlet>

...

...

      <servlet-mapping>

        <servlet-name>LongPollingServlet30API</servlet-name>

        <url-pattern>*.cclongpolling30API</url-pattern>

      </servlet-mapping>

 

Long Polling – The “Web socket way”

(This function is only available within the RISC HTML Client – it is not available in the Java-Swing and Java-FX based client.)

In the meantime web sockets are a common, proven way to setup communication between the browser client and the backend server. As consequence they are also used to communicate server side events to the client. The way, web sockets are used for “long polling” eventing is following the same procedure then explained with normal long polling:

Example

The example in the demo workplace demonstrates a parallel thread sending events to the polling mechanism.

The layout is:

<t:beanprocessing id="g_1">

    <t:websocketpolling id="g_2"

        actionListener="#{d.DemoWebSocketPolling.onWebSocketAction}"

        duration="100"

        websocketurl="#{d.DemoWebSocketPolling.webSocketUrl}" />

</t:beanprocessing>

<t:rowbodypane id="g_3" rowdistance="5">

    <t:row id="g_4">

        <t:label id="g_5" text="URL" width="100" />

        <t:label id="g_6" cutwidth="true"

            text="#{d.DemoWebSocketPolling.webSocketUrl}" width="100%" />

    </t:row>

    <t:row id="g_7">

        <t:coldistance id="g_8" width="100" />

        <t:button id="g_9"

            actionListener="#{d.DemoWebSocketPolling.onStopAction}"

            text="Stop thread processing" />

        <t:coldistance id="g_10" width="5" />

        <t:button id="g_11"

            actionListener="#{d.DemoWebSocketPolling.onRestartAction}"

            text="Restart thread processing" width="100+" />

    </t:row>

    <t:row id="g_12">

        <t:textarea id="g_13" height="100%"

            text="#{d.DemoWebSocketPolling.protocol}" width="100%" />

    </t:row>

</t:rowbodypane>

 

The Java implementation is:

package workplace;

 

import java.io.Serializable;

 

import org.eclnt.editor.annotations.CCGenClass;

import org.eclnt.jsfserver.elements.util.Trigger;

import org.eclnt.jsfserver.polling.LongPollingMgr;

import org.eclnt.jsfserver.polling.websocket.DefaultLongPollingWebSocket;

import org.eclnt.workplace.IWorkpageDispatcher;

import org.eclnt.workplace.WorkpageDefaultLifecycleListener;

import org.eclnt.workplace.WorkpageDispatchedPageBean;

 

@CCGenClass (expressionBase="#{d.DemoWebSocketPolling}")

 

public class DemoWebSocketPolling

    extends WorkpageDispatchedPageBean

    implements Serializable

{

    // ------------------------------------------------------------------------

    // inner classes

    // ------------------------------------------------------------------------

 

    public class MyThread extends Thread

    {

        boolean i_threadStop = false;

        @Override

        public void run()

        {

            for (int i=0; i<100; i++)

            {

                try { Thread.sleep(500); } catch (Throwable t) {}

                if (i_threadStop == true) break;

                m_counter++;

                m_longPollingWebSocket.wakeup(true);

            }

            if (m_thread == this) m_thread = null;

        }

    }

    

    // ------------------------------------------------------------------------

    // members

    // ------------------------------------------------------------------------

    

    DefaultLongPollingWebSocket m_longPollingWebSocket;

    MyThread m_thread;

    int m_counter = 0;

    String m_protocol = "Protocol:\n";

 

    // ------------------------------------------------------------------------

    // constructors & initialization

    // ------------------------------------------------------------------------

 

    public DemoWebSocketPolling(IWorkpageDispatcher workpageDispatcher)

    {

        super(workpageDispatcher);        

        m_longPollingWebSocket = new DefaultLongPollingWebSocket();

        LongPollingMgr.add(m_longPollingWebSocket);

        getWorkpage().addLifecycleListener(new WorkpageDefaultLifecycleListener()

        {

            @Override

            public void reactOnDestroyed()

            {

                if (m_thread != null)

                    m_thread.i_threadStop = true;

                LongPollingMgr.remove(m_longPollingWebSocket);

            }

        });

        m_thread = new MyThread();

        m_thread.start();

    }

 

    public String getPageName() { return "/workplace/demowebsocketpolling.jsp"; }

    public String getRootExpressionUsedInPage() { return "#{d.DemoWebSocketPolling}"; }

 

    // ------------------------------------------------------------------------

    // public usage

    // ------------------------------------------------------------------------

    

    public String getWebSocketUrl() { return m_longPollingWebSocket.getURL(); }

    public String getProtocol() { return m_protocol; }

    public void setProtocol(String value) { this.m_protocol = value; }

 

    public void onWebSocketAction(javax.faces.event.ActionEvent event)

    {

        m_protocol += "\nCurrent counter: " + m_counter;

    }

 

    public void onStopAction(javax.faces.event.ActionEvent event)

    {

        if (m_thread != null)

            m_thread.i_threadStop = true;

        m_thread = null;

    }

 

    public void onRestartAction(javax.faces.event.ActionEvent event)

    {

        if (m_thread != null)

            return;

        m_thread = new MyThread();

        m_thread.start();

    }

}

 

Which LONGPOLLING way to choose?

The clear decision which way to choose is simple:

Long Operations with Messages

Based on long polling and on the usage of a modal popup there is a nice way of defining long operations that run within a processing thread on server side and which pass text messages into an observer-object.

Please have a look into the demo workplace “General Issues > Server Events > Long operations” for viewing an example.

The application using “long operations” needs to define two “Runnable” instances:

For writing messages, so that the user gets notified about the status of the processing, there is an observer object, that provides a corresponding interface. The messages are transferred to the client, internally using long polling as described in the previous chapter.

The following shows the code of the example that is available within the demo workplace:

    public void onStartOperation(ActionEvent event)

    {

        final IObserver observer = LongOperationWithObserverPopup.prepare("My long Demo Operation");

        Runnable longOperation = new Runnable()

        {

            public void run()

            {

                for (int i=0; i<5; i++)

                {

                    observer.addMessage("Running... ("+i+")");

                    try

                    {

                        Thread.currentThread().sleep(3000);

                    }

                    catch (Throwable t) {}

                }

            }

        };

        Runnable finishOperation = new Runnable()

        {

            public void run()

            {

                Statusbar.outputSuccess("Finished!");

            }

        };

        LongOperationWithObserverPopup.run(longOperation,finishOperation);

    }

 

The long operation is a simple loop, containing some sleep-statements in order to simulate some long processing. The second operation is a simple output to the status bar.

Please pay attention: with the utility class “LongOperationWithObserverPopup” you can only start one long operation on server-side, within the scope of one user session. Nesting of long operations will lead into an error situation.

Using “Hot Deployment”

This chapter provides information about an optional feature of the server infrastructure of CaptainCasa Enterprise Client: “Hot Deployment”.

Overview + When to Use

The typical development process when developing user interfaces is:

While changes in .jsp/.xml files are recognized by the tool environment immediately (with the next refreshing), changes in the Java classes are recognized in the following way:

Due to the restarting of the web application the runtime environment works on refreshed classes, i.e. the changes that you did within your Java code are visible.

Now, where's the problem?

The solution of this problem is quite easy: these classes that have to do with UI processing and that are very likely to be changed during the development process need to be separated from these classes which are not changed so frequently. Typical scenarios are:

The runtime environment of CaptainCasa Enterprise Client is able to separate the classes by using different class loaders. The “stable” classes are running in the normal web application class loader (the one using WEB-INF/classes and WEB-INF/lib), and the “volatile” classes are running in an extra class loader, named “UI Hot Deployment Class Loader”.

The UI Hot Deployment Class Loader is a child of the Web Application class loader. This means it knows all the classes from its parent. But, vice versa, the Web Application class loader does not know any classes of the UI Hot Deployment Class Loader.

This typically makes sense and is in sync with your normal class dependencies: the UI classes can access logical classes, but logical classes never must access UI classes.

 

Now having separated UI classes and logical classes you already might guess what the advantage is: you do not have to restart the whole web application in order to see changes that you did within your UI classes. It's enough to restart the UI Hot Deployment Class Loader. This takes much less time than restarting the web application.

If enabling the hot deployment for your project, then within the Layout Editor there will be a “Hot Deploy” button next to the “Reload” button for the fast hot-deploying of UI classes:

.

Hot UI Deployment Framework Details

Resolution of Managed Beans

The central location where the separated class loader is used is the resolution of managed beans that are defined in the faces-config.xml file. In the JSF / CaptainCasa server side framework there is a so called “VariableResolver” that creates managed bean instances for names defined in face-config.xml. CaptainCasa comes with an own implementation of “VariableResolver” that loads resolved objects within the context of an own classloader.

Configuration

The “UI Hot Deployment Classloader” is not used by default. It requires some extra configuration.

Direct Configuration

There is some direct configuration of the hot deployment that you may call from the CaptainCasa toolset:

A dialog will show up:

In the dialog you can switch on/off the hot deployment. If switched on, then you may either decide to include all packages into the hot deployment – or you may add these packages that you explicitly want to add. When selecting one package, then automatically all sub-packages will be added as well.

This configuration dialog will update two configuration files, one for controlling the runtime, the other one for controlling the deployment process of the project.

Details: Configuration of Runtime

There is a configuration file “eclntjsfserver/config/hotdeploy.xml” that controls the class loading. If the file is available then class loading will be done using the “UI Hot Deployment Classloader”. You may copy the file from the template “hotdeploy.xml_template” that comes with the CaptainCasa delivery (webcontentcc).

The default content is:

<!--

Configuration for the optional hot deployment framework. The directories

that are listed + their containing jar files are added to the classpath

of the hot deployment classloader.

-->

 

<hotdeploy>

  <webappdir name="/eclnthotdeploy/classes"/>

  <webappdir name="/eclnthotdeploy/lib"/>

</hotdeploy>

 

In the XML definition the directories where the class loader searches for UI classes are listed. Please always use this default configuration.

You may add additional directories as well. A directory can be defined in two ways:

Details: Configuration of your Project

The packages that are part of the hot deployment are part of the normal project configuration file (“.ccproject” within your project root directory:

<project ...>
...

  <hotdeploymentpackage name="com.xyz.packageA"/>

  <hotdeploymentpackage name="com.xyz.packageB"/>

  ...

</project>

 

Details: What internally happens

Within your project the sources are compiled into the WEB-INF/classes folder of your project.

When deploying your application (i.e. “Reload”) then normally (without hot deployment) all web content of the project is directly copied into the Tomcat runtime. This means: also the WEB-INF/classes directory is copied.

Now, with hot deploymnet, the content of WEB-INF/classes is not directly copied, but all the content that is part of the hot-deployment-packages is copied into the directory /eclnthotdeploy/classes of the runtime – and the rest is copied as usual into the WEB-INF/classes directory. - In other words: the deployment process that is triggered by the tool is a bit more sophisticated and copies the classes into different directories.

Embedding “other” classes/libraries for Hot Deployment

Parts of this already were mentioned in the previous chapter – and are pointed out in this one.

Default way

You may add any directory – in inside the web content or outside the web content to the Hot Deployment management.

Example:

<hotdeploy>

  <webappdir name="/eclnthotdeploy/classes"/>

  <webappdir name="/eclnthotdeploy/lib"/>

 

  <webappdir name=”/mysuperclasses/”/>

  <dir name=”c:/myspecialclasses/classes”/>

  <dir name=”c:/myspecialclasses/lib”/>

</hotdeploy>

 

(Please do not touch “/eclnthotdeploy/classes” and “/eclnthotdeploy/lib”!).

Each directory that you add is treated in two ways:

With versioned directory

<hotdeploy>

  <webappdir name="/eclnthotdeploy/classes"/>

  <webappdir name="/eclnthotdeploy/lib"/>

 

  <webappdir name=”/mysuperclasses/${latest}/”/>

  <dir name=”c:/myspecialclasses/${latest}/classes”/>

  <dir name=”c:/myspecialclasses/${latest}/lib”/>

</hotdeploy>

 

By embedding the term “${latest}” into the directory definition you can set up scenarios in which you have a certain versioning in the corresponding directory location. The “${latest}”-term is replaced by the latest directory that is available – the “latest” is the one which is alphabetically the last one.

Example:

In the scenario above you have the following directory structure:

c:

  myspecialclasses

    20201031

      classes

      lib

    20201102

      classes

      lib

    20201116

      classes

      lib

    20201224

      classes

      lib

 

If now the definition in the hotdeploy.xml file is...

<hotdeploy>

  ...

  <dir name=”c:/myspecialclasses/${latest}/classes”/>

  <dir name=”c:/myspecialclasses/${latest}/lib”/>

  ...

</hotdeploy>

 

...then “${latest}” will be replaced by “20201224” because it is the last directory in alphabetical order.

Using environment/system variables

In the name of directories you can mebed environment / system variables by using the patterns:

${sys.xxxx}

${env.xxxx}

 

System variables are resolved by Java function “System.getProperty(...)”. Environment variables are resolved by function “System.getenv(...)”.

Accessing “Hot deployed classes” from “WEB-INF/classes”

There are situations in which you use other frameworks which assume that your code runs on “WEB-INF/class” level.

Example: Your code is structured into the following pagackes, all of them are hot deployed:

com.xxxxx.yyyyy.data   => contains data structures and data access

com.xxxxx.yyyyy.logic => contains logic

com.xxxxx.yyyyy.ui     => contains UI managed beans

 

Now your application should provide some REST service and you decide to e.g. use the Jersey framework (or any other one) to support this.

The Jersey libraries must be installed in WEB-INF/lib because they must reachable for the web application classloader. And: the classes that you add to the Jersey framework must run within the same classloader as Jersey.

So the overall deploy situation is:

<yourwebapp>

  eclnthotdeploy

    classes

      com

        xxxxx

          yyyyy

            data

            logic

            ui

  WEB-INF

    classes

      com

        xxxxx

          yyyyy

            restapi <== you REST implementations

    lib

      Jersey*.jar

 

The problem now is:

Solution 1 – Move all your logic packages “up”

You may arrange your class structure in the following way...

<yourwebapp>

  eclnthotdeploy

    classes

      com

        xxxxx

          yyyyy

            ui

  WEB-INF

    classes

      com

        xxxxx

          yyyyy

            data

            logic

            restapi <== you REST implementations

    lib

      Jersey*.jar

 

...so that the data and logic packages are moved to the web application classloader level. As consequence they are directly visible for both the REST-processing and for the UI-processing (which still runs in the hot deploy classloader).

Advantage:

Disadvantage:

Solution 2 – Define some interface in between

You may arrange you class in the following way...

<yyourwebapp>

  eclnthotdeploy

    classes

      com

        xxxxx

          yyyyy

            restapi

              impl

                XYZServiceImplementation.class

            data

            logic

            ui

  WEB-INF

    classes

      com

        xxxxx

          yyyyy

            restapi

              def

                IXYZServiceInterface.class
               XYZServiceInterfaceLoader.class

    lib

      Jersey*.jar

 

Inside your code you define some explicit interface, here “IXYUServiceInterface”. The interface definition is done on web application class loader level.

The implementation of the interface is eon in some separate class “XYZServiceImplementation”.

When accessing the interface on web application class loader level you need to go though an explicit loader:

public class XYZInterfaceLoader

{

    public IXYZServiceInterface load()

    {

        // the following does NOT work!!!

        // return new XYZServiceImplementation();

 

        // so instead:
       Class c = Class.forName

        (

            “com.xxxxx.yyyyy.restapi.impl.XYZServiceImplementation”,

            true,

            HotDeployManager.currentClassloader()

        );

        IXYZServiceInterface result = (IXYZServiceInterface)c.newInstance();

    }

}

 

You see:

Triggering Update of Hot Deployment Classloader

The Hot Deployment Classloader is the one to load the UI classes. Hot deployment on the one hand means that you copy the updated web application from your project into the (e.g. Tomcat) runtime. And it on the other hand means that the runtime receives a certain trigger so that it knows that it has to reload certain classes.

Hot Deployment via CaptainCasa Toolset

The default way (and the one described in the introduction of this chapter) is to use the CaptainCasa toolset: by pressing the “Hot Deploy” button within the Layout Editor the project files are copied into the (Tomcat) runtime – and then the page within the preview area is refreshed in a way (using a certain URL parameter) that tells the Hot Deployment Classloader to reload its content.

Hot Deployment from “outside” by updating a file (e.g. via ANT-script)

In addition there is a possibility to trigger an update of the Hot Deployment Classloader from outside:

By updating the content of the following file...

tomcat/

  webapps/

    <webapplication>/

      eclnthotdeploy/

        .cctrigger

 

...the hot deployment will be started with the next new session that is created on server side.

As consequence you can integrate the hot deployment into your build infrastructure:

Please pay attention: you really have to change the content of the .cctrigger-file! Changing the modified-time-stamp of the file is not sufficient.

Hot Deployment from “outside” by URL

By appending query parameter “cc_hotdeploy=true” to the .risc-URL starting a page, the hot deploymenet is triggered.

Example:

http://localhost:8080/demos/workplace.workplaceRisc.risc?cc_hotdeploy=true

 

 

Design time – run time

Hot deployment is based on the fact that at design time the classes are NOT taken over into the WEB-INF/classes directory of the servlet engine – but that they are copied into a /eclnthotdeploy/classes directory of your web content. This special copying is done by the deployment functions within the CaptainCasa toolset.

When deploying your webcontent to some “real” environment, you typically create a .war file in which all classes are part of the WEB-INF/classes directory. You may use the ANT-script “<project>/build/buid_war.xml” in order to do this, but of course may also use any other mechanism as well. Consequence: hot deployment is only active within your design time environment, not in your production environment.

In addition to his you may also remove the file “eclnjsfserver/config/hotdeploy.xml” - so that hot deployment is deactivated even if you should by accident deliver some class files in the “/eclnthotploy/classes”-directory. The ANT script “build/build_war.xml” that is created as part of your project contains a corresponding section:

<copy todir="${build.targetdirectory}/webcontent" overwrite="true">

    <fileset dir="../webcontent">

        <include name="**/**"/>

        <!--

        The file triggering the hot deployment is explicitly removed

        so that hot deployment is switched off by defult.

        -->

        <exclude name="eclntjsfserver/config/hotdeploy.xml"/>

        <!--

        Here you may add some exclude statements for specific files that

        should not be copied into the delivery. Example: subversion files

        <exclude name="**/.svn"/>   

         -->

    </fileset>

    <fileset dir="../webcontentbuild">

        <include name="**/**"/>

    </fileset>

    <fileset dir="../webcontentcc">

        <include name="**/**"/>

    </fileset>

</copy>

 

 

 

Security Issues

CaptainCasa Enterprise Client is based on standards:

This does not completely unburden you from checking certain security considerations which have to be applied to any http scenarios. This chapter lists these issues that typically are checked when going through security audits and tells you how to react on technical side correspondingly.

Usage of https is mandatory for production systems!

All traffic between browser and server might go through infrastructures that you are not really aware about! Consequence: sending data without encryption and sending data without any technical authentication of the server is a no-go!

Do never use “http://” in production environments. Always go through “https://”!

Define the pages that you want to be directly start-able

A “.risc” URL starts the corresponding page on server side.

Typically you want to define dedicated entrance pages into your application system – and you do not want to allow the user to “free-style” open any page that you provide.

To check this there is an own mechanism that is part of the CaptainCasa server runtime:

There is an interface “IStartPageChecker”:

package org.eclnt.jsfserver.starter;

 

import javax.servlet.http.HttpServletRequest;

 

public interface IStartPageChecker

{

    public boolean checkIfPageCanBeDirectlyStarted(HttpServletRequest startRequest, String page);

}

 

There is a default implementation “DefaultStartPageChecker” that is always active.

This default implementation is checking for definitions inside the system.xml configuration file:

<system>

     <riscstarter

         ...

         >

        

        ...

        <allowstart page="/abc/*"/>

        <allowstart page="/*/index.*"/>

        <allowstart page="/xxx/yyy.jsp"/>

        ...

        <excludestart page="/abc/*"/>

        <excludestart page="/*/index.*"/>

        <excludestart page="/xxx/yyy.jsp"/>

        ...

        

     </riscstarter>

</system>

 

Here you have two options:

Of course: adding one “allowstart” definition will automatically over-rule all “excludestart” definitions! So you need to either do “allowstart” definitions or “excludestart” definitions – doing both does not make sense.

You can use the wildcard character.

If you want to set up some own implementation of “IStartPageChecer” then you can do so – we recommend to extend your implementation from CaptainCasa's default implementation. You nee to register your implementation in system.xml:

<system>

      ...

     <riscstarter

        ...

        startpagecheckerclassname="<class name of IStartPageChecker implementation>"

        ...

        >

        ...

        ...

     </riscstarter>

</system>

 

Please pay attention:

General protection of session-related data access by cookie

Session-id “hijacking”

All application data is kept on server-side within an http-session. There are two types of session management (see chapter “Session Management”):

So, the http-session is the core-session in both scenarios. Access from outside into this session needs to be secured.

In case of using the URL-based session management, the session-id is directly “steal-able” by a man in the middle, because obviously the URL of a request is not part of the encryption of https. - In both session management scenarios, tricky people might find out the session-id, e.g. by using JavaScript injection or by accessing log information in which the session-id might be output – or whatever.

Solution

CaptainCasa provides a simple but efficient approach to generally restrict the access to session-related content.

The cookie value is protected in the following way:

Technical information

Protection of session-related data access by post-parameter

Solution

A similar function is/was used for observing session-related communication between client and server which is based on a parameter which is managed per session and which is part of the post-data of a request.

The principal behind is the same as described in the previous chapter: an additional id for the session is communicated between client and server – and is checked on server-side.

Because this function is not based on Cookies but on post-data (request) and header-data (response), the functions are working with the Swing/FX client, too.

Technical information

Fine-tuning the SecurityFilter

The SecurityFilter provides an interface by which you can influence its behavior. The interface is defined inside the SecurityFilter-class itself:

public interface IExtension

{

    public boolean checkIfToExecuteCheck(ServletRequest request);

}

 

You can add an implementation of the interface either by defining the corresponding class in the system.xml configuration file...

<system ...>

    ...

    <securityfilter extensionclassname="workplace.SecurityFilterExtension"/>

    ...

</system>

 

...or by directly setting an instance through the method:

SecurityFilter.setSecurityFilterExtension(IExtension extension)

 

The interface method “checkIfToExecuteCheck(...)” is processed prior to any processing of the security filter. When returning “false” then the security filter will not check the corresponding request.

What is a situation in which an over-ruling the SecurityFilter is reasonable?

Example: you start an inner CaptainCasa dialog inside an outer dialog by using the SUBPAGE component. In this case the inner dialog runs decoupled from the outer dialog in an own Html-IFRAME. The SUBPAGE component is able to keep track of the session id that is used in the inner dialog – in order to remember when the inner dialog is hidden and then re-shown (e.g. at an other place of the screen). If not implementing an extension of the security filter, then there will be a error message if the SUBPAGE is shown the second time, because there is a IFRAME-browser-instance accessing the session with a pre-selected session id.

Example for implementing a SecurityFilter.IExtension class

The demo workplace provides a demo implementation:

package workplace;

 

import javax.servlet.ServletRequest;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpSession;

 

import org.eclnt.jsfserver.util.SecurityFilter;

 

public class SecurityFilterExtension

    implements SecurityFilter.IExtension

{