Meeting 7

Aus Java Student User Group Austria - Java + JVM in Wien Österreich / Vienna Austria
Wechseln zu: Navigation, Suche

When: Monday, November 24th, 2008 - 19:00

Where: Freihaus HS4

This time we got two demonstrations of slightly different (web) frameworks. On the one side the web application framework Seam using EJB3 and JSF, on the other side Flash based Rich Internet Applications developed with Flex. They both have in common that they use components as the building block and declare them in XML.

Download the introduction slides (pdf, 1.2MB).


RIAs with Adobe Flex

Logo

Download the slides (pdf, 0.9MB).
You can also view the slides online.
Download the sourcecode as *.zip (2.1MB).

Agenda

  • Preface
    • Definition of the term "Filthy Rich Clients"
    • Limitation of using traditional JavaScript vs Flash-based technologies
  • Getting Started
    • Some hard facts
    • Simple ActionScript example
  • 8 Demonstrations
    • Hello World - Styling components using external CSS
    • Listener & Binding - Handling (user-) events and bind variables
    • ExternalInterface - Invoke JavaScript functions from ActionScript
    • XmlRequest - Use the DataGrid to display XML fetched via HTTPService
    • Transitions - Animate state transitions with the ViewStack
    • Cairngorm - Write a common registration form in a good (MVCS) style
    • BlazeDS - Let ActionScript and Java talk to each other
    • AIR App - Create your first Flex desktop application
  • Summary
    • Technologies overview (see links below)
    • Diagram of the MVC pattern provided by Cairngorm
    • Appendix: ActionScript compared to the Java language

Content

"Although amazing things have been accomplished within the confines of JavaScript, using technologies like Ajax, JSON, GWT etc., these are nonethless confines. We bump up against their limit every day, and those limits are not going away. I believe that to solve the user interface problem, we need the equivalent of a DSL dedicated to the user experience. For me, Flash-based technologies like Flex are the best solution to this problem." -- Bruce Eckel, Author of Thinking in Java


"Filthy Rich Clients are application so graphically rich that they ooze cool, they suck the user in from the outset and hang onto them with a death grip of excitement. They force the user tell their friends about the application." -- Chet Haase


Christoph Pickl gave an introduction to Adobe Flex. As its predecessor Flash itself is a collection of tools (Flash Player, Flash authoring), Flex is also more than a single product but consists of an open source SDK, an ActionScript 3 application framework, an Eclipse based IDE called Flex Builder, XML based declarative language, the Flash Player for web applications and the AIR runtime for desktop applications. With additionals pieces of software like Red5 (an open source RTMP server for streaming, recording) or BlazeDS (open source alternative for remoting and messaging capabilities of Adobe's LifeCycle Data Services) you can even natively talk to a powerful backend which is written in a totally different language (Java, C#, PHP, ...) or just exchange plain XML so any language will suite.

A simple ActionScript example (looks just like Java, doesnt it?): <source lang="actionscript"> package at.jsug {

import logging.Logger;

public class Demo extends AbstractDemo implements IDemo {

   // private static final logger, just as you are used to it with apache commons logging/log4j
   private static const LOG: Logger = Logger.getLogger("at.jsug.Demo");
   // every class member must be declared (either it is a var/const or a function)
   private var name: String;
   // constructor
   public function Demo(name: String) {
       this.name = name;
   }
   public function myOperation(value: Number): int {
       LOG.info("myOperation(value=" + value + ")");
       return value / 2;
   }
   
   // mandatory override keyword, nice ;)
   public override function toString(): String {
       return "Demo[name=" + this.name + "]";
   }

}} </source>


Demos

Hello World

HelloWorld.xml: <source lang="xml"> <?xml version="1.0" encoding="utf-8"?> <mx:Application layout="vertical" xmlns:mx="http://www.adobe.com/2006/mxml">

   <mx:Style source="helloWorld.css" />
   <mx:Label text="Hello World!" />

</mx:Application> </source>

helloWorld.css: <source lang="css"> Application {

   background-color: white;
   vertical-align: middle;

} Label {

   color: black;
   font-size: 40;
   font-weight: bold;

} </source>


Listener & Binding

Listener.mxml: <source lang="actionscript"> <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">

   <mx:Script>
       <![CDATA[
           import mx.controls.Button;
           private function onClick(event: MouseEvent): void {
               const label: String = (event.currentTarget as Button).label;
               trace("button with label [" + label + "] clicked");
           }
       ]]>
   </mx:Script>
   <mx:Button label="Click me" click="onClick(event)" />

</mx:Application> </source>

ListenerBinding.mxml: <source lang="actionscript"> <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">

   <mx:Script>
       <![CDATA[
           import mx.controls.Button;
           [Bindable]
           private var boundString: String = "";
           private function onClick(event: MouseEvent): void {
               const label: String = (event.currentTarget as Button).label;
               this.boundString = "button with label [" + label + "] clicked";
           }
           ]]>
   </mx:Script>
   <mx:Label text="{this.boundString}" />
   <mx:Button label="Click me" click="onClick(event)" />

</mx:Application> </source>


JavaScript Invocation

JavaScript.mxml: <source lang="actionscript"> <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">

   <mx:Script>
       <![CDATA[
           private function doJs(): void {
               ExternalInterface.call("doInvokeExternalJavascript", 42);
           }
       ]]>
   </mx:Script>
   <mx:Button label="Invoke JavaScript Function" click="doJs()" />

</mx:Application> </source>

javascript.js: <source lang="javascript"> // include this file in project/html-template/index.template.html: // <script src="javascript.js" language="javascript"></script>

function doInvokeExternalJavascript(meaningOfLife) { alert('Meaning of life: ' + meaningOfLife); } </source>


XmlRequest

XmlRequest.mxml: <source lang="actionscript"> <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"

   creationComplete="onCreationComplete()">
   <mx:Script>
       <![CDATA[
           import mx.rpc.http.HTTPService;
           import mx.rpc.events.FaultEvent;
           import mx.rpc.events.ResultEvent;
           import mx.collections.ArrayCollection;
           [Bindable]
           private var personsData: ArrayCollection = new ArrayCollection();
           private var personsService: HTTPService;
           private function onCreationComplete(): void {
               this.personsService = new HTTPService();
               this.personsService.url = "persons.xml";
               this.personsService.addEventListener(ResultEvent.RESULT, this.onPersonsResult);
               this.personsService.addEventListener(FaultEvent.FAULT, this.onPersonsFault);
               this.personsService.send();
           }
           private function onPersonsResult(event: ResultEvent): void {
               this.personsData = event.result.persons.person as ArrayCollection;
           }
           private function onPersonsFault(event: FaultEvent): void {
               throw new Error("ERROR: " + event);
           }
       ]]>
   </mx:Script>


   <mx:DataGrid dataProvider="{this.personsData}" />

</mx:Application> </source>


Transitions

Transitions.mxml: <source lang="xml"> <?xml version="1.0" encoding="utf-8"?> <mx:Application layout="vertical" horizontalAlign="center" verticalAlign="middle" xmlns:mx="http://www.adobe.com/2006/mxml">

   <mx:ApplicationControlBar width="100%">
       <mx:ToggleButtonBar dataProvider="{this.viewStack}" width="100%" />
   </mx:ApplicationControlBar>
   <mx:ViewStack id="viewStack" width="100%" height="100%">
       <mx:Canvas label="Switch Red" width="100%" height="100%" backgroundColor="#FF0000" showEffect="WipeDown" hideEffect="WipeUp" />
       <mx:Canvas label="Switch Green" width="100%" height="100%" backgroundColor="#00FF00" showEffect="WipeDown" hideEffect="WipeUp" />
       <mx:Canvas label="Switch Blue" width="100%" height="100%" backgroundColor="#0000FF" showEffect="WipeDown" hideEffect="WipeUp" />
   </mx:ViewStack>

</mx:Application> </source>


Cairngorm

Controller.as: <source lang="actionscript"> package jsug.cairngorm {

import com.adobe.cairngorm.control.FrontController;

public class Controller extends FrontController {

   public function Controller() {
       this.addCommand(FoobarEvent.EVENT_ID, FoobarCommand);
   }

} } </source>

FoobarEvent.as: <source lang="actionscript"> package jsug.cairngorm {

import com.adobe.cairngorm.control.CairngormEvent;

public class FoobarEvent extends CairngormEvent {

   public static const EVENT_ID: String = "foobarEvent";
   public function FoobarEvent(bubbles:Boolean=false, cancelable:Boolean=false) {
       super(EVENT_ID, bubbles, cancelable);
   }
   public override function toString():String {
       return "FoobarEvent[]";
   }

} } </source>

FoobarCommand.as: <source lang="actionscript"> package jsug.cairngorm {

import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent;

import mx.controls.Alert;

public class FoobarCommand implements ICommand {

   public function FoobarCommand() {
       // nothing to do
   }
   public function execute(_event: CairngormEvent): void {
       trace("FoobarCommand.execute()");
       // event actually not needed - const event: FoobarEvent = _event as FoobarEvent;
       const p: Person = ModelLocator.instance.person;
       const message: String = "My name is " + p.lastName + "! " + p.firstName + " " + p.lastName + ". ";
       const title: String = "Greetings";
       Alert.show(message, title);
   }

} } </source>

JsugCairngorm.mxml: <source lang="actionscript"> <mx:Application layout="vertical" horizontalAlign="center" verticalAlign="middle"

   xmlns:cairngorm="jsug.cairngorm.*" xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*">
   <cairngorm:Controller />
   <mx:Script>
       <![CDATA[
           import mx.events.ValidationResultEvent;
           import jsug.cairngorm.FoobarEvent;
           import jsug.cairngorm.ModelLocator;
           private function onSendPerson(): void {
               trace("Application.onSendPerson()");
               // most important line; dispatches cairngorm event and maps to FoobarCommand
               new FoobarEvent().dispatch();
           }
       ]]>
   </mx:Script>
   <local:PersonPanel sendPerson="onSendPerson()" />

</mx:Application> </source>

... for full sourcecode download attached zip ...


BlazeDS

Person.as: <source lang="actionscript"> package {

[Bindable] [RemoteClass(alias="jsug.blazeds.Person")] public class Person {

   public var id: int;
   public var firstName: String;
   public var lastName: String;
   public function Person() { }

} } </source>

PersonService.as: <source lang="actionscript"> package {

import mx.collections.ArrayCollection; import mx.controls.Alert; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import mx.rpc.remoting.RemoteObject;

/* this class extends a remote object which could have alternatively declared in XML as follows: <mx:RemoteObject id="personService" destination="personService" showBusyCursor="true"> <mx:method name="selectAllPersons" result="onPersonsResult(event)" fault="onPersonsFault(event)" /> </mx:RemoteObject>

  • /

public dynamic class PersonService extends RemoteObject {

   public function PersonService() {
       this.destination = "personService"; // defined in remoting-config.xml file down below
       this.addEventListener(ResultEvent.RESULT, this.onResult);
       this.addEventListener(FaultEvent.FAULT, this.onFault);
   }
   public function doSelectAllPersons(): void {
       this.selectAllPersons();
   }
   private function onResult(event: ResultEvent): void {
       Model.instance.persons = event.result as ArrayCollection;
   }
   private function onFault(event: FaultEvent): void {
       Alert.show(String(event.message), "Person Service Error");
   }

} } </source>

Person.java: <source lang="java"> package jsug.blazeds;

public class Person {

   private int id;
   private String firstName;
   private String lastName;
   public Person(final int id, final String firstName, final String lastName) {
       this.id = id;
       this.firstName = firstName;
       this.lastName = lastName;
   }
   @Override
   public String toString() {
       return "Person[id=" + this.id + "]";
   }
   // getters/setters omitted

} </source>

PersonService.java: <source lang="java"> package jsug.blazeds;

import java.util.Date; import java.util.LinkedList; import java.util.List;

public class PersonService {

   public List<Person> selectAllPersons() {
       System.out.println("PersonService.selectAllPersons() invoked at " + new Date());
       final List<Person> persons = new LinkedList<Person>();
       persons.add(new Person(0, "Christoph", "Pickl"));
       persons.add(new Person(1, "Florian", "Motlick"));
       return persons;
   }

} </source>

remoting-config.xml: <source lang="xml"> <?xml version="1.0" encoding="UTF-8"?> <service id="remoting-service" class="flex.messaging.services.RemotingService">

   <adapters>
       <adapter-definition id="java-object" class="flex.messaging.services.remoting.adapters.JavaAdapter" default="true"/>
   </adapters>
   <default-channels>
       <channel ref="my-amf"/>
   </default-channels>
   <destination id="personService">
       <properties>
           <source>jsug.blazeds.PersonService</ source>
           <scope>application</scope>
       </properties>
   </destination>

</service> </source>

... for full sourcecode download attached zip ...


AIR App

JsugCompleteAir.mxml: <source lang="xml"> <?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication layout="vertical" xmlns:mx="http://www.adobe.com/2006/mxml">

   <mx:FileSystemTree id="tree" width="100%" height="100%" directory="{File.userDirectory}"/>
   <mx:Label text="File: {this.tree.selectedItem.name}" width="100%" />

</mx:WindowedApplication> </source>

JsugCompleteAir-app.xml: <source lang="xml"> <?xml version="1.0" encoding="UTF-8"?> <application xmlns="http://ns.adobe.com/air/application/1.0">

   <id>JsugCompleteAir</id>
   <filename>JsugCompleteAir</filename>
   <name>JsugCompleteAir</name>
   <version>v1</version>
   <initialWindow>
       <content>[This value will be overwritten by Flex Builder in the output app.xml]</content>
       <title>AIR App</title>
       <width>400</width>
       <height>400</height>
   </initialWindow>

</application> </source>


Links

Libraries, Frameworks

Other Flex Presentations

Seam Demo

Download the slides (pdf, 27KB).
You can also view the slides online.

Content

Florian Motlik gave a short demo of the JBoss Seam web application framework. It is built on top of EJB3 and integrates JSF and Wicked. Furthermore it heavily relies on dependency injection (to be precisely it makes use of "bijection").

Bijection works with the easy use of annotations: You use @Out to put a variable into a scope and @In to insert a variable from the scope into a SessionBean (the variable names is taken as the identifier).

You primarily work with two type of beans: A SessionBean represents the controller of the MVC pattern and takes input from the user and acts upon that data (they can be either stateful or stateless). On the other side, an EntityBean is something you persist in the database (with the help of hibernate, JPA or any other similar persistence framework).

Examples

A seam entity bean: <source lang="java"> @Entity @Name("user") @Scope(SESSION) @Table(name="users") public class User implements Serializable {

   private static final long serialVersionUID = 1881413500711441951L;
  
   private String name;
   private String username;
   private String password;
  
   public User(final String name, final String password, final String username) {
       this.name = name;
       this.password = password;
       this.username = username;
   }
  
   public User() {
       // nothing to do
   }
  
   @NotNull @Length(min=5, max=15)
   public String getPassword() {
       return this.password;
   }
   public void setPassword(final String password) {
       this.password = password;
   }
  
   @NotNull
   public String getName() {
       return this.name;
   }
   public void setName(final String name) {
       this.name = name;
   }
  
   @Id @NotNull @Length(min=5, max=15)
   public String getUsername() {
       return this.username;
   }
   public void setUsername(final String username) {
       this.username = username;
   }

} </source>


A seam session bean: <source lang="java"> @Stateless @Name("register") public class RegisterAction implements Register {

   @In
   private User user;
  
   @PersistenceContext
   private EntityManager em;
  
   @Logger
   private Log log;
   
   public String register() {
       final String sql = "select username from User where username=#{user.username}";
       final List existing = em.createQuery(sql).getResultList();
        
       if(existing.size() == 0) {
           em.persist(user);
           log.info("Registered new user #{user.username}");
           return "/registered.jsp";
       }
       FacesMessages.instance().add("User #{user.username} already exists");
       return null;
   }

} </source>

Links