Creating a new UI module

Introduction

The UI part of the QualiPSo Factory is compiled using Maven (generating a WAR file) and is included in a global EAR file which can be compiled using Maven too (via the factory-assembly module). That packaging can be done thanks to the Servlet 3.0 specification (especially the web fragment feature) which has been included in JBoss 6.0.0.M2 recently. So, to integrate a new module inside the QualiPSo Factory, two Maven modules has to be developed (one for the server part and one for the UI part). The main topic of those guidelines is about the User Interface development.

The communication between UI modules and server side modules is done using RMI (Java Remote Method Invocation).

GWT is used to develop the UI views. All graphical modules are small web apps including servlet server side and javascript client side to allow gwt components to communicate with the server using ajax calls. The integration of the UI modules (servlet composition) can be done by using a web-fragment.xml file inside each module.

Factory UI modules, as EJB modules, include unit and functional tests. Functional tests are using Selenium and are located in an other module because they imply the deployment of the application.

In the following guidelines, the factory-ui-service-project module will be used as the main example.

General_architecture

By the Maven archetype feature, you will be able to get an empty project with the right architecture. So, in the shell console, go to the working directory where the source code will be stored and then type that command line:

mvn archetype:generate -DarchetypeCatalog=http://radis.loria.fr/maven2/repo/archetype-catalog.xml

The general architecture of the UI project has to be respected in order to allow the integration of your module inside the whole factory. The imported module is designed as presented in the table below (for our example, we still use the Project module) :

+- factory-ui-service-project/Module root
    +- src/main/java/Java sources root
    l     +- project/Module name root
    l     l     +- Project.gwt.xmlXml file defining the module
    l     l     +- client/Client folder root
    l     l     l     +- iserver/Servlet communication (javascript to servlet)
    l     l     l     l     +- ProjectServlet.javaInterface of the module servlet
    l     l     l     l     +- ProjectServletException.javaCustom exception for servlet use
    l     l     l     +- model/Java version of UI objects transferred between servlet and client navigator
    l     l     l     l     +- Project.javaJava object used in the views (lighter than the server one)
    l     l     l     +- presenter/Logical part of the module
    l     l     l     l     +- ProjectPresenter.javaEntry point (manages all views, handles UI module events, declares views to other UI modules and receives views actions)
    l     l     l     +- view/View part (ie. forms, buttons, ...)
    l     l     l     l     +- DisplayCreateEditView.javaJava side of a view
    l     l     l     l     +- DisplayCreateEditView.ui.xml"Gwt-xml" side of a view
    l     l     l     +- resources/Physical resources files (ie. css, jpg, gif, ...)
    l     l     l     l     +- ProjectResources.javaJava interface for accessing physical resources
    l     l     l     l     +- css/Css folder root
    l     l     l     l     l     +- Style.javaJava interface for accessing css styles (can be accessed from the "gwt-xml" files)
    l     l     l     l     l     +- style.cssCss style sheet (gwt custom tags can be used)
    l     l     l     l     +- img/Img folder root
    l     l     +- dev/Dev folder root
    l     l     l     +- ProjectDev.gwt.xmlXml file defining the module (for dev mode only, cf. Debugging)
    l     l     +- server/Server folder root
    l     l     l     +- ProjectServletImpl.javaImplementation of the module servlet
    +- src/main/resources/Resources folder root
    l     +- META-INF/web-fragment.xmlXml file defining the servlet mapping (for production mode)
    +- src/main/webapp/Webapp folder root
    l     +- WEB-INF/Html content (for dev mode only)
    l     l   +- web.xmlXml file defining the servlet mapping (for dev mode only, cf. Debugging)
    l     +- index.htmlHtml file defining the view being debugged (for dev mode only, cf. Debugging)
    +- target/generated-sources/gwtGwt generated sources folder root
    l     +- iserver/ProjectServletAsync.javaAsynchronous version of the servlet interface (generated by gwt)

OpenParts_guidelines

General Configuration

First of all, the module configuration has to be set (in Eclipse). To do so, right-click on the Module root and select "Properties".

Build path configuration

Those folders have to be added :

PathOutput folder
"src/main/java"default ("target/classes")
"src/main/resources""war/WEB-INF/classes"
"src/main/webapp"default ("target/classes")
"src/test/java""target/test-classes"
"target/generated-sources/gwt"default ("target/classes")

 

Screenshot of the Build path configuration step
GWT configuration

The checkbox "Use Google Web Toolkit" has to be set.

Screenshot of the GWT configuration step

Development rules

In order to ensure the compliance with the OpenParts framework and so to ensure an auto-integration of your module, the following files have to be developed as detailed. For each file, you can find the rules to be respected and a code example.

Main
Project.gwt.xml

The content of this file must be as below :

  1. Inherits the QGWT project
  2. Inherits the OpenParts project
  3. Inherits the GWT RPC project
  4. Defines a folder to find all gwt client classes (compiled in javascript at the end)
  5. Defines the entry point of the module
<module rename-to='project'>
[1]     <inherits name='org.qualipso.factory.ui.shared.QGWT.QGWT' />
[2]     <inherits name='org.qualipso.factory.ui.shared.OpenParts.OpenParts' />
[3]     <inherits name='com.google.gwt.rpc.RPC' />
        
[4]     <source path='client' />
    
[5]     <entry-point class='org.qualipso.factory.ui.service.project.client.presenter.ProjectPresenter' />
</module>
Servlet
META-INF/web-fragment.xml

That xml file is used for declaring the servlet of the module. Thanks to Servlet 3.0, that war composition is now allowed. !! MORE EXPLANATIONS NEEDED!!

<web-fragment 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-fragment_3_0.xsd"
        version="3.0">
        
        <name>FactoryUiServiceProject</name>
        
        <servlet>
                <display-name>ProjectServlet</display-name>
                <servlet-name>ProjectServlet</servlet-name>
                <servlet-class>org.qualipso.factory.ui.service.project.server.ProjectServletImpl</servlet-class>
        </servlet>
        
        <servlet-mapping>
                <servlet-name>ProjectServlet</servlet-name>
                <url-pattern>/project/project</url-pattern>
        </servlet-mapping>
</web-fragment>
ProjectServlet.java

That Java file is the interface where the servlet methods have to be defined. Mainly, create, display, update and delete methods are the ones to be declared.

public interface ProjectServlet extends RpcService {
    
        public void createProject(String path, String name, String summary, String license) throws ProjectServletException;

        (...)
}
ProjectServletAsync.java

That Java file is the auto-generated asynchronous interface based on the ProjectServlet.java interface. It's generated by a GWT compilation (cf. compilation instructions to know how to compile). That allows asynchronous communication between client and server parts.

public interface ProjectServletAsync
{
        void createProject( java.lang.String path, java.lang.String name, java.lang.String summary, java.lang.String license, AsyncCallback<Void> callback );
        
        (...)
}
ProjectServletImpl.java

That Java file is the implementation of the ProjectServlet.java interface. Here is the real code where UI part is calling Server part services [1]. It's also here where converters [2] between server objects and UI objects have to be done (cf. Project.java model object for the UI part).

@SuppressWarnings("serial")
public class ProjectServletImpl extends RpcServlet implements ProjectServlet {

        private static Log logger = LogFactory.getLog(ProjectServletImpl.class);
        private ProjectService projectService;
        private BrowserService browserService;

        @Override
        public void init(ServletConfig config) throws ServletException {
                super.init(config);
                try {
                        projectService = (ProjectService) Factory.findService(ProjectService.SERVICE_NAME);
                        browserService = (BrowserService) Factory.findService(BrowserService.SERVICE_NAME);
                } catch (FactoryException e) {
                        logger.error("unable to get project service", e);
                        throw new ServletException(e);
                }
        }

        @Override
        public void createProject(String path, String name, String summary, String license) throws ProjectServletException {
                logger.debug("createProject called in the servlet...");
                try {
[1]                     projectService.createProject(path, name, summary, license);
                } catch (Exception e) {
                        logger.error("unable to create project", e);
                        throw new ProjectServletException(e.getMessage());
                }
        }
        
        (...)
        
[2]     private static Project projectFromQualipsoProject(org.qualipso.factory.project.entity.Project qproject) {
                Project project = new Project();
                project.setPath(qproject.getResourcePath());
                project.setName(qproject.getResourceName());
                project.setLicence(qproject.getLicence());
                (...)
                return project;
        }
}
Model objects
Project.java

That Java file is contained in the "model/" folder where all the UI model objects are grouped. Here is an example of an object used in the Project module : a project has a name, a licence, etc. Only getters and setters are written here. The converter between that UI object and the server one has to be written in the ProjectServletImpl.java file.

@SuppressWarnings("serial")
public class Project implements Serializable {

        private String path;
        private String name;
        private String licence;
        
        public Project() {
        }
        
        public String getPath() {
                return path;
        }

        public void setPath(String path) {
                this.path = path;
        }
        
        (...)
}
Views

For example, CRUD operations (create, read, update, delete) for a Project resource implies 4 views (one for each action). Each of those views are declared using the @OPViewFactory annotation inside the Presenter.java and a couple of a "gwt-xml" UI file (ie. DisplayCreateEditView.ui.xml) and a java UI file (ie. DisplayCreateEditView.java) are necessary. These operations will allow to dynamically load standard view from anywhere in the UI.

ProjectPresenter.java

That Java file is the entry point of the module. Its goal is to manage all the project views, to handle UI module events, to declare views to other UI modules and to receive views actions.

Those annotations have to be used :

[1] @OPServiceNameDeclare a class (an EntryPoint class) as the entry point of the module
[2] @OPViewFactoryDeclare a view to be integrated inside the whole factory. Two parameters are mandatory :  
  String resourceName: resource name  
  String actionName: action name can be picked in the constant attributes of OPShell (ie. OPShel.ACTION_CREATE)  
[3] @OPEventHandlerDeclare an event handling method that can be called by any other module via the OPShell (OPShell.dispatchEventTo()). No specific events or an event or an array of events can be specified.

To produce an event, you can use a specific class defined in the OpenParts framework: OPSHell, which is a kind of helper class including a lot of actions in the framework. Here are the three methods accessible :

OPShell.displayView()Call to OPShell for displaying a view of an external module for an embbed use (you can only display core views, otherwise you should be dependant of other unknown modules)
OPShell.dispatchEvent()Call to OPShell for dispatch an event (declared in a nomenclature). Two parameters are mandatory :  
  String event: event name (ie. "CHANGE_PATH", "UPDATE_PATH", "REFRESH")  
  OPParams params: a couple of name and value  
OPShell.dispatchEventTo()Call to OPShell for dispatch an event to a specified module. Three parameters are mandatory :  
  String service: external module service name ie. "project"  
  String event: event name (ie. "CUSTOM_EVENT")  
  OPParams params: a couple of name and value  

!!! ADD EXAMPLES INSIDE THAT PRESENTER OF ALL THE ANNOTATIONS AND OPSHELL CALLS !!!

[1]@OPServiceName("project")
public class ProjectPresenter implements EntryPoint {

        public interface ProjectBinder extends OPBinder<ProjectPresenter> {}
        public final static ProjectBinder binder = GWT.create(ProjectBinder.class);

        private final ProjectServletAsync projectServlet = GWT.create(ProjectServlet.class);
        private DisplayCreateEditView dceView = null;
    
[2]     @OPViewFactory(resourceName="project", actionName=OPShell.ACTION_CREATE)
        public Widget loadDisplayCreateEditView(OPParams params){
                dceView = new DisplayCreateEditView(this);
                return dceView;
        }
        
        @OPViewFactory(resourceName = "project", actionName = "menu")
        public Widget loadProjectMenuActionItem(final OPParams params) {
                return new ProjectMenuActionItem(this);
        }

        @Override
        public void onModuleLoad() {
                GWT.log("Loaded Project", null);
                binder.bindPart(this);
                binder.notifyLoadingCompleted();
        }
        
[3]     ???

}
DisplayCreateEditView.ui.xml

That "gwt-xml" file is the presentation part of a view. A view should be defined for each action and for each resource. Some QGWT components are used in that view. To be able to access to the QGWT framework, just declare the entry point of the QGWT project [1] and then use custom tags (cf. QGWT guidelines for more information).

If you are using standard GWT components, you must use only Layout components (cf. the official GWT documentation), otherwise it won't be displayed.

To be able to access to custom styles or physical resources, you have to declare the ProjectResources.java [2] which allows getting the specified resources using Java accessors.

!!! TO BE UPDATED DEPENDING ON INRIA : ONLY 1 VIEW FOR CRUD?? !!!

<ui:UiBinder    xmlns:ui="urn:ui:com.google.gwt.uibinder"
        xmlns:g="urn:import:com.google.gwt.user.client.ui"
[1]     xmlns:q="urn:import:org.qualipso.factory.ui.shared.QGWT.client.ui">
        
[2]     <ui:with field='resources' type='org.qualipso.factory.ui.service.project.client.resources.ProjectResources' />
        
        <q:QTabLayoutPanel ui:field="tabLayout">
        
                <tab name="Generic">
                        <q:QFormPanel>
                                <elem name='Name' type='text' exemple='Qualipso'>Project Name</elem>
                                <elem name='Development Status' type='text' exemple='finished'>Development Status</elem>
                                <elem name='Description' type='area' exemple='Ultimate forge project'>Project Descrition</elem>
                        </q:QFormPanel>
                </tab>
                
                <tab name="Topics">
                        <q:QFormPanel>
                                <elem name='Topics' type='text' exemple='forge;java'>Topics</elem>
                        </q:QFormPanel>
                </tab>
                
                <tab name="Languages">
                        <q:QFormPanel>
                                <elem name='Programming Languages' type='text' exemple='c++;java'>Programming Languages</elem>
                                <elem name='Spoken Languages' type='text' exemple='english; french'>Spoken Languages</elem>
                        </q:QFormPanel>
                </tab>
                
                <tab name="Audiences">
                        <q:QFormPanel>
                                <elem name='Audiences' type='text' exemple='Loic Vourch'>Audiences</elem>
                        </q:QFormPanel>
                </tab>
                
                <tab name="Operating System">
                        <q:QFormPanel>
                                <elem name='OS' type='text' exemple='MacOS'>Operating system used</elem>
                        </q:QFormPanel>
                </tab>
                
        </q:QTabLayoutPanel>
        
</ui:UiBinder>
DisplayCreateEditView.java

That Java file is the behavior handling part of a view. It mainly does calls to the ProjectPresenter.java. It must extends Composite for allowing integration in the whole UI.

public class DisplayCreateEditView extends Composite{

        interface DisplayCreateEditUiBinder extends UiBinder<Widget, DisplayCreateEditView>{}
        private static DisplayCreateEditUiBinder uiBinder = GWT.create(DisplayCreateEditUiBinder.class);
        
        @UiField
        ProjectResources resources;
        @UiField
        QTabLayoutPanel tabLayout;
        
        
        private ProjectPresenter presenter;
        
        public DisplayCreateEditView(ProjectPresenter projectPresenter){
                initWidget(uiBinder.createAndBindUi(this));
                resources.style().ensureInjected();
                presenter = projectPresenter;
                
                for(Widget w : tabLayout.getPanels()){
                        ((QFormPanel)w).setOkButtonHandler(new OkHandler());
                }
        }

        private class CreateHandler implements ClickHandler{
                @Override
                public void onClick(ClickEvent arg0) {
                        for (Widget w : tabLayout.getPanels())
                                ((QFormPanel)w).empty();
                }
        }
        
        private class OkHandler implements ClickHandler{
                @Override
                public void onClick(ClickEvent arg0) {
                        for (Widget w : tabLayout.getPanels())
                                ((QFormPanel)w).ok();
                }
        }
}
Resources

Two ways are available for accessing physical resources.

The first one is the standard one, which is that you make an http request each time you want to get a resource. !! MORE EXPLANATIONS NEEDED!!

The second one is the specificity of GWT : resources are encapsuled inside the css or js files and so, are surely loaded inside the client browser. !! MORE EXPLANATIONS NEEDED!!

ProjectResources.java

That Java file is an interface for accessing physical resources directly in the views files.

public interface ProjectResources extends ClientBundle {

        @Source("org/qualipso/factory/ui/service/project/client/resources/css/style.css")
        Style style();
        
        @Source("org/qualipso/factory/ui/service/project/client/resources/img/loader.gif")
        ImageResource iconLoader();
        
        @Source("org/qualipso/factory/ui/service/project/client/resources/img/accept.png")
        ImageResource iconGood();
        
        (...)
}
Style.java

That Java file is an interface that allows accessing css style classes directly in the views files (ie. DisplayCreateEditView.ui.xml and DisplayCreateEditView.java). Those accessors must be the same as the css classes written in the style.css file.

public interface Style extends CssResource {

        String mycustomstyle();
        
        (...)
}
style.css

That css file defines custom classes for display. You can also use specific GWT tags.

.mycustomstyle {
    color: black;
    border: 1px solid #ffffff;
    background-color: white;
}

QGWT_guidelines

To be done