Meeting 7

Aus JSUG

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


Inhaltsverzeichnis

[Bearbeiten] 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).

[Bearbeiten] 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

[Bearbeiten] 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?):

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 + "]";
    }
}}


[Bearbeiten] Demos

[Bearbeiten] Hello World

HelloWorld.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>

helloWorld.css:

Application {
    background-color: white;
    vertical-align: middle;
}
Label {
    color: black;
    font-size: 40;
    font-weight: bold;
}


[Bearbeiten] Listener & Binding

Listener.mxml:

<?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>

ListenerBinding.mxml:

<?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>


[Bearbeiten] JavaScript Invocation

JavaScript.mxml:

<?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>

javascript.js:

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


[Bearbeiten] XmlRequest

XmlRequest.mxml:

<?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>
 
    <!-- alternatively could have created and configured instance via MXML:
    <mx:HTTPService
        id="personsService"
        url="persons.xml"
        result="onPersonsResult(event)"
        fault="onPersonsFault(event)"
    />
    -->
 
    <mx:DataGrid dataProvider="{this.personsData}" />
 
</mx:Application>


[Bearbeiten] Transitions

Transitions.mxml:

<?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>


[Bearbeiten] Cairngorm

Controller.as:

package jsug.cairngorm {
 
import com.adobe.cairngorm.control.FrontController;
 
public class Controller extends FrontController {
    public function Controller() {
        this.addCommand(FoobarEvent.EVENT_ID, FoobarCommand);
    }
}
}

FoobarEvent.as:

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[]";
    }
}
}

FoobarCommand.as:

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

JsugCairngorm.mxml:

<mx:Application layout="vertical" horizontalAlign="center" verticalAlign="middle"
    xmlns:cairngorm="jsug.cairngorm.*" xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*">
 
    <!-- have to load controller manually by simply instantiating it -->
    <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>

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


[Bearbeiten] BlazeDS

Person.as:

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

PersonService.as:

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

Person.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
 
}

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

remoting-config.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>

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


[Bearbeiten] AIR App

JsugCompleteAir.mxml:

<?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>

JsugCompleteAir-app.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>


[Bearbeiten] Links

[Bearbeiten] Libraries, Frameworks

[Bearbeiten] Other Flex Presentations

[Bearbeiten] Seam Demo

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

[Bearbeiten] 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).

[Bearbeiten] Examples

A seam entity bean:

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


A seam session bean:

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

[Bearbeiten] Links

Persönliche Werkzeuge