Ellipse Tutorial

Development of a generating events component

AccueilNotre catalogue de formationsNos partenairesDemande de devisEllipse FrameworkJWT (Javascript Widget Toolkit)License d'exploitation de nos logicielsVos développements sur mesuresTutorial sur le langage CSSTutorial sur le langage XMLTutorial sur le langage JavaTutorial sur le langage Visual Basic 6.0Historique de la sociétéNous contacterA propos de ce site
 

WARNING: This tutorial is being written! Do not hesitate to report any errors or suggestions.

Quick access:
   Some Reminders about the Java listeners
   Introduction to the management of event model of the Ellipse framework
   Specifying an Ellipse listening interface and an Ellipse event class
   Generating an event inside an Ellipse component
   Ellipse events and management of events priorities
   Interception of an event
   Treatment of events generators

Some Reminders about the Java listeners

The JavaBeans model (we have discussed about it in previous chapters) introduced a Java coding convention. This convention provides, through the Java Reflection engine and the engine of introspection (a wrapper for the Java reflection), to discover, at the runtime, the properties (see the previous paper), but also the events managers: it is on this last point that we will be debating in the first part of this document.

The listeners model is based on the concept of interface. To implement a particular events kind (a type), the first step is to define a listening interface. The component, of the event generator, will make his headphones through this interface and the receivers of the event will implement it. For information, please note that a listening interface should normally extend the java.util.EventListener interface (directly or indirectly) and it must also end with the word Listener.

 
01 public interface DemoListener extends java.util.EventListener {
02     public void eventFired( DemoEvent event );
03 }
Definition of an listening interface

As you have certainly noticed, the possible methods of the interface (here one: eventFired), which will be invoked to deal with the events, accept an event object as a parameter. This aspect is also imposed by JavaBeans. An event class must expand the java.util.EventObject class. This class defined an attribute to locate the issuer subject of this event. Here is a small example code for our DemoEvent class.

 
01 public class DemoEvent extends java.util.EventObject {
02 
03     public DemoEvent( Object source ) {
04         super( source );
05     }
06 
07 }
Definition of an event class

Once the event class and the listening interface are coded, it is necessary to encode the starting of the event at the level of the component that generates the event. Normally the event is started in a particular condition: we will ignore this condition. We will only focus on the starting.

 
01 private Vector<DemoListener> listeners = new Vector<DemoListener>();
02 
03 public void fireDemoEvent() {
04     DemoEvent event = new DemoEvent( this/*, ...*/ );
05     for( DemoListener listener : this.listeners ) {
06         listener.eventFired( event );
07     }
08 }
Example of event publishing code

It remains only to encode the listener in question: the one who receives the event. Indeed, most of the time, your code is simply a listener from one type of a particular event. It is more unusual to encode an event generator. Anyway, here's an example of code which allow reacting to the receipt of an event (note the use of an anonymous class).

 
01 generator.addDemoListener( new DemoListener() {
02     @Override public void eventFired( DemoEvent event ) {
03         System.out.println( "Event received !" );
04     }
05 });
Example of implementing a listener

Introduction to the management of event model of the Ellipse framework

One of the major contributions of modern Web frameworks is to allow an event programming, on the server side, for the development of Web applications. The Ellipse framework is not an exception to the rule. Compared to some other Java web framework, Ellipse fully uses the skill of Java listeners, with a traditional record of the listeners. An extension linked to the Web architecture has been introduced at the starting of events. We will come back to it later.

To really understand the overall functioning of the Ellipse event management, we will code a Web component of monthly selection releasing an event since the selected month will be changed. Remember that the following extracts code provide from the demonstration application of the Ellipse Framework. You can download the application by activating this link.

Specifying an Ellipse listening interface and an Ellipse event class

We shall therefore begin by defining the type of event as well as our listening interface relatively to the selection of a new month. An Ellipse events class must extend the corelib.services.web.webapplications.events.WebEvent class: this class proceeds itself from java.util.EventObject. We will call this class MonthSelectorEvent and we will add a property to easily retrieve the selected month: we shall call this property newMonth (a get and a set should therefore be defined).

 
01 package corelib.services.web.samples.virtualcaddy.webcomponents.samples;
02 
03 import corelib.services.web.webapplications.events.WebEvent;
04 
05 public class MonthSelectorEvent extends WebEvent {
06 
07     private String newMonth;
08 
09     public MonthSelectorEvent( Object source, String newMonth ) {
10         super( source );
11         this.setNewMonth( newMonth );
12     }
13 
14     public String getNewMonth() {
15         return newMonth;
16     }
17 
18     public void setNewMonth( String newMonth ) {
19         if ( newMonth == null ) throw new NullPointerException();
20         this.newMonth = newMonth;
21     }
22 
23 }
Class "corelib.services.web.samples.virtualcaddy.webcomponents.samples.MonthSelectorEvent"

Now we can define our listening interface. We will call this interface MonthSelectorListener and it will extend the interface corelib.services.web.webapplications.events.WebListener. As explained in the first part of this document, the method defined by our method must accept a parameter of type of the associated event (specified by the JavaBeans model).

 
01 package corelib.services.web.samples.virtualcaddy.webcomponents.samples;
02 
03 import corelib.services.web.webapplications.events.WebListener;
04 
05 public interface MonthSelectorListener extends WebListener {
06 
07     public void monthChanged( MonthSelectorEvent event );
08 
09 }
Class "corelib.services.web.samples.virtualcaddy.webcomponents.samples.MonthSelectorListener"

Generating an event inside an Ellipse component

Now we need to implement our component (at least its class). We begin by seeing the complete code of the class. Then, we will resume the key points one by one.

 
01 package corelib.services.web.samples.virtualcaddy.webcomponents.samples;
02 
03 import java.io.PrintWriter;
04 import java.util.Vector;
05 
06 import corelib.services.web.components.VisualComponent;
07 import corelib.services.web.components.events.WebComponentEventDescriptor;
08 import corelib.services.web.components.events.WebComponentEventLevel;
09 import corelib.services.web.webapplications.events.WebPageEvent;
10 
11 public class MonthSelector extends VisualComponent {
12 
13     private static final String [] MONTHS = {
14         "--SELECT--", "January", "February", "March", "April", "Mai", "June",
15         "July", "August", "September", "October", "November", "December" 
16     };
17 
18 
19     private String value = MonthSelector.MONTHS[0];
20     public String getValue() { return value; }
21     public void setValue( String value ) { this.value = value; }
22 
23     private String oldMonth = MonthSelector.MONTHS[0];
24     public String getOldMonth() { return oldMonth; }
25     public void setOldMonth( String oldMonth ) { this.oldMonth = oldMonth; }
26 
27 
28     private Vector<MonthSelectorListener> listeners = new Vector<MonthSelectorListener>();
29 
30     public void addMonthSelectorListener( MonthSelectorListener listener ) {
31         if ( listener == null ) throw new NullPointerException();
32         listeners.add( listener );
33     }
34 
35     public void fireMonthSelectorEvent() {
36         MonthSelectorEvent event = new MonthSelectorEvent( this, value );
37         for( MonthSelectorListener listener : listeners ) {
38             listener.monthChanged( event );
39         }
40     }
41 
42     @Override public void component_load( WebPageEvent event ) {
43         if ( value.equals( oldMonth ) == false  && 
44                 this.webPage.getRequest().getMethod().equals( "POST" ) ) {
45             WebComponentEventDescriptor eventDescriptor =
46                 new WebComponentEventDescriptor(
47                     this, "MonthSelectorEvent", WebComponentEventLevel.DEFAULT
48                 );
49             this.webPage.getEventDispatcher().addEventDescriptor( eventDescriptor );
50         }
51     }
52 
53     @Override public void component_renderBegin( WebPageEvent webPageEvent ) {
54         PrintWriter writer = this.getWebPage().getOut();
55         writer.println( "<select name=\"" + this.getId() + "\">" );
56         for( String month : MonthSelector.MONTHS ) {
57             String sel = month.equals( value ) ? " selected" : "";
58             writer.println( "<option " + sel + ">" + month + "</option>" );
59         }
60         writer.println( "</select>" );
61         writer.println( "<input type=\"hidden\" name=\"" + this.getId()
62                 + ":oldMonth\" value=\"" + value + "\">" );
63     }
64 
65 }
Class "corelib.services.web.samples.virtualcaddy.webcomponents.samples.MonthSelector"

The line 21 begins by defining a collection of type Vector<MonthSelectorListener>: it will allow storing all the objects that have signed up as listeners on the event released by our component. To realize these entries, the method addMonthSelectorListener, reported on line 30, is proposed. It accepts, of course, as a parameter an object of type MonthSelectorListener.

The Ellipse framework, applies a life cycle relatively complex to each treatment of a WebPage. The invocation of events managers is one of the points of this life cycle. It results that the calls to the listeners are not executed when the starting conditions are met, but when there are delayed in time.

In line 42, the method component_load achieves the test that helps to determine whether or not the selected month has changed since the previous request. To know if the value has changed, a covert form field (<input type="hidden" ...>) was associated: it contains the last selection. The value of this field is carried by the property oldMonth (lines 24 and 25) because the covert field name is [componentName]:oldMonth (see line 62). So, if the two values (month and oldMonth) are different, it is because the event should be released. We must therefore prepare the event starting: this is done by lines 45-49. We describe the event that will be released: what is the release for this event, what its type is and particularly what his priority is. Indeed, the Ellipse framework describes five levels of priority for the component WEB events (we will return to this point in a few moments). Finally, the descriptor is added to the EventDispatcher which will be responsible to spread all events relating to the web components and then at the good time in the life cycle of the web page.

Then, the line 35 defined the method fireMonthSelectorEvent: it will be invoked by the Ellipse framework at the released of the event. It follows that his name is also an agreement. But this is not the JavaBeans model which specifies the name of this method but the Ellipse framework. We can deduce a second thing: an Ellipse listening interface will hardly explain more than one method. Any method of Ellipse events starting on a component must begin by fire. As for its content, the methodology creates an event object and invokes for each listener, the method waited, passing the event.

Ellipse events and management of events priorities

To certify the cohesion in the starting of your events, the Ellipse framework introduces the concept of achievement priorities. The different levels of priorities are introduced by the class WebComponentEventLevel: there are five levels. The order of priority is the same as this one of their definitions in the enumerated type presented above. This is first the priority events WebComponentEventLevel.FIRST that are released, and then those of priority WebComponentEventLevel.BEFORE and so on. In contrary for all events using one same priority, we can not predict the starting order.

 
01 package corelib.services.web.components.events;
02 
03 public enum WebComponentEventLevel {
04 
05     FIRST, BEFORE, DEFAULT, AFTER, LAST
06 
07 }
Class "corelib.services.web.components.events.WebComponentEventLevel"

For information, please note that the events of type corelib.services.web.components.events.FormInputEvent (in case of changing of the value written by a form field) have a priority WebComponentEventLevel.BEFORE. The events of type corelib.services.web.components.events.ActionEvent (in case of activating a form button) have a priority WebComponentEventLevel.AFTER.

Intercepting an event

The interception of an Ellipse event happens exactly as the same way as for a SWING application. You just have to register your listener on the Web component considered and if this last one releases events, your listener will receive it. You need to encode your listeners’ recordings in the event page_load on the web page considered. The following code corresponds to the Web page class of demonstration.

01 package corelib.services.web.samples.virtualcaddy.webpages.samples;
02 
03 import corelib.services.web.components.OutputText;
04 import corelib.services.web.samples.virtualcaddy.webcomponents.samples.MonthSelector;
05 import corelib.services.web.samples.virtualcaddy.webcomponents.samples.MonthSelectorEvent;
06 import corelib.services.web.samples.virtualcaddy.webcomponents.samples.MonthSelectorListener;
07 import corelib.services.web.webapplications.WebPage;
08 import corelib.services.web.webapplications.events.WebPageEvent;
09 
10 public class EventCoding extends WebPage {
11 
12     private MonthSelector monthSelector;
13     private OutputText txtSelectedMonth;
14 
15     @Override public void page_load( WebPageEvent webPageEvent ) {
16         monthSelector.addMonthSelectorListener( new MonthSelectorListener() {
17             @Override public void monthChanged( MonthSelectorEvent event ) {
18                 txtSelectedMonth.setText( event.getNewMonth() );
19             }
20         });
21     }
22 
23 }
Class "corelib.services.web.samples.virtualcaddy.webpages.samples.EventCoding"

Now you can see the XML description of the Web page. Of course this one is associated with the class of the page above. Note that to make the recording of the listeners, the web components must be identified in the Web page and, more importantly, be defined in the page class (line 12 of the above example).

01 <?xml version="1.0" encoding="ISO-8859-1" ?>
02 <web:Html xmlns:web="corelib.services.web.components"
03 		  xmlns:samples="corelib.services.web.samples.virtualcaddy.webcomponents.samples"
04           codeBehind="corelib.services.web.samples.virtualcaddy.webpages.samples.EventCoding">
05 	<head>
06 		<title>Event Coding Sample</title>
07 		<link rel="stylesheet" type="text/css" href="../CssStyles.css" />
08 	</head>
09 	<body>
10 		<h1>Event Coding Sample</h1> <br />
11 	
12 		<web:Form method="POST">
13 			<samples:MonthSelector id="monthSelector" />
14 			<web:Button type="submit" value="Submit" /> <br/>
15 			
16 			Your selection is : <web:OutputText id="txtSelectedMonth" />
17 		</web:Form>
18 	</body>
19 </web:Html>
File "samples/EventCoding.wp"

Treatment of events generators

To close this chapter, we must know that it is possible to repeat several times a component or several Ellipse components through the use of a <web:Repeater>. But in this case, how to allocate to these replicated components a manager of single event? In fact the solution is simple, the framework provides a useful method: EventUtility.addActionListener. The code below shows you the definition of the web page. It offers to you to enter a numeric value: the server will create as many buttons as requested. If, in addition, you click on one of these buttons, an event manager will be released.

01 <?xml version="1.0" encoding="ISO-8859-1" ?>
02 <web:Html xmlns:web="corelib.services.web.components"
03 		  xmlns:samples="corelib.services.web.samples.virtualcaddy.webcomponents.samples"
04           codeBehind="corelib.services.web.samples.virtualcaddy.webpages.samples.EventCoding2">
05 	<head>
06 		<title>Event Coding Sample</title>
07 		<link rel="stylesheet" type="text/css" href="../CssStyles.css" />
08 	</head>
09 	<body>
10 		<h1>Event Coding Sample</h1> <br />
11 	
12 		<web:Form method="POST">
13 			Button count : <web:TextBox id="txtButtonCount" value="3" autoPost="true" />
14 			<br /><br />
15 			
16 			<web:Repeater values="#{this.buttonNames}" elementAlias="name">
17 				<web:Button id="btnTest" value="#{name}" /> <br/>
18 			</web:Repeater> 
19 		</web:Form>
20 	</body>
21 </web:Html>
File "samples/EventCoding2.wp"

Now you can see the code of the page class associated with the Web page presented above. Note that the line 24 which associates a single event manager to all instances of buttons that have been replicated by the repeater (it is necessary to do so, to provide the name of the components as well as the number of instances).

01 package corelib.services.web.samples.virtualcaddy.webpages.samples;
02 
03 import corelib.services.web.components.events.ActionEvent;
04 import corelib.services.web.components.events.ActionListener;
05 import corelib.services.web.components.events.EventUtility;
06 import corelib.services.web.webapplications.WebPage;
07 import corelib.services.web.webapplications.events.WebPageEvent;
08 
09 public class EventCoding2 extends WebPage {
10 
11     private int length = 3;
12 
13     @Override public void page_init( WebPageEvent webPageEvent ) {
14         // --- Necessary because of the life cycle of the Web page ---
15         // The DataBinding is made before the assignment of values
16         // stored in the HTTP request !!!
17         try {
18             length = Integer.parseInt( request.getParameter( "txtButtonCount" ) );
19         } catch( Exception exception ) {
20         }
21     }
22 
23     @Override public void page_load( WebPageEvent webPageEvent ) {
24         EventUtility.addActionListener( this, "btnTest", length, new ActionListener() {
25             @Override public void actionPerformed( ActionEvent event ) {
26                 System.out.println( "Event intercepted " + event.getRepeatedIndex() );
27             }
28         });
29     }
30 
31     public String [] getButtonNames() {
32         String [] names = new String[ length ];
33         for( int i=0; i<length; i++ ) names[i] = "Button " + i;
34         return names;
35     }
36 
37 }
Class "corelib.services.web.samples.virtualcaddy.webpages.samples.EventCoding2"