Seam and Flex integration
william.drai Oct 16, 2008 5:53 PMHi all,
We have just released Granite Data Services 1.1.0, a LGPL alternative to Adobe LiveCycle Data Services, which includes the first stable release of the Tide integration package for Seam.
Tide aims at providing to the Flex client a transparent integration with server Seam components, with a client programming model similar to Seam remoting in JavaScript. After an initial setup, it requires close to no configuration during development and makes developing Flex applications very natural for people used to Seam/JSF/facelets.
Tide integrates with most features of Seam : component bijection, events, conversations, security with Identity component. For example, a Tide Flex client component can get injected with values that have been outjected by a server component, or can register as a listener for Seam events raised by server components (even asynchronously thanks to the integration with the GraniteDS Gravity push library).
Finally Tide provides advanced data management features. It allows to use Seam Query components as paged data providers for Flex UI components (DataGrids, Lists...) and can transparently initialize lazy collections. This GDS release includes Granite Eclipse builder, an Eclipse plugin which can automatically generate the ActionScript entity classes from the Java JPA/EJB3 model.
You can download two sample projects :
- a port of Seam booking to Flex (without any modification on the original Seam components)
- a simple Address Book, demonstrating simple CRUD functionality with data paging and lazy loading of collections (using only basic Seam Home and Query components)
Here is a small example from the hotel booking project showing the use of context injection in Flex.
Seam component :
@Stateful @Name("hotelSearch") @Scope(ScopeType.SESSION) @Restrict("#{identity.loggedIn}") public class HotelSearchingAction implements HotelSearching { @PersistenceContext private EntityManager em; private String searchString; private int pageSize = 10; ... @DataModel private List<Hotel> hotels; public void find() { page = 0; queryHotels(); } ... private void queryHotels() { hotels = em.createQuery("select h from Hotel h where lower(h.name) like #{pattern} or lower(h.city) like #{pattern} or lower(h.zip) like #{pattern} or lower(h.address) like #{pattern}") .setMaxResults(pageSize) .setFirstResult( page * pageSize ) .getResultList(); } public List<Hotel> getHotels() { return this.hotels; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } @Factory(value="pattern", scope=ScopeType.EVENT) public String getSearchPattern() { return searchString==null ? "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%'; } public String getSearchString() { return searchString; } public void setSearchString(String searchString) { this.searchString = searchString; } ... }
Flex/Tide component :
[Bindable] [Name("hotelsCtl", restrict="true")] public class HotelsCtl { // The [In] annotation indicates that this variable will be injected with a proxy of the Seam component named 'hotelSearch'. [In] public var hotelSearch:Object; // The [Observer] annotation indicates that this method is an event handler. It will listen for events raised by other components or by the UI. [Observer("searchForHotels")] public function searchForHotels(searchString:String, pageSize:Number):void { // Set the variables to inject in the hotelSearch component hotelSearch.searchString = searchString; hotelSearch.pageSize = pageSize; // Remote call of the 'find' method of the 'hotelSearch' Seam component hotelSearch.find(); } }
Flex/Tide UI :
<mx:Application initialize="tideContext.mainAppUI = this;"> ... <mx:Script> <![CDATA[ // Initialize the Tide context [Bindable] private var tideContext:Context = Seam.getInstance().getSeamContext(); // Register our client component Seam.getInstance().addComponents([HotelsCtl]); [Bindable] public var pageSizes:ArrayCollection = new ArrayCollection([5, 10, 20]); // The [In] annotation indicates that this 'hotels' variable will be injected with the Seam component/context variable named 'hotels' whenever a Seam component outjects it [Bindable] [In] public var hotels:ArrayCollection; ]]> </mx:Script> ... <mx:HBox direction="horizontal" paddingBottom="5" paddingLeft="0" paddingRight="0" paddingTop="5" width="100%"> <mx:TextInput id="txtQueryString" width="200" color="#C0C0C0" /> <mx:ComboBox id="cmbResultSize" width="58" x="0" y="0" upSkin="myCombobox" downSkin="myComboboxDown" overSkin="myComboboxOver" disabledSkin="myComboboxDisabled" dataProvider="{pageSizes}"/> <mx:Spacer width="5"/> <!-- Raises an event which will trigger our Observer client component --> <mx:Button label="Search" x="0" y="0" click="dispatchEvent(new TideUIEvent('searchForHotels', txtQueryString.text, Number(cmbResultSize.selectedLabel)));" upSkin="myButton" downSkin="myButtonDown" overSkin="myButtonOver" disabledSkin="myButtonDisabled"/> </mx:HBox> ... <mx:VBox height="360" width="100%" styleName="boxBordersSubbgSkin"> <mx:Box backgroundColor="#e2e5d5" borderColor="#c0c0c0" styleName="tabssub" width="100%"> <mx:DataGrid id="hotelSearchResults" rowCount="6" width="100%" dataProvider="{hotels}"> <mx:columns> <mx:DataGridColumn itemRenderer="components.ImageRender" width="25" /> <mx:DataGridColumn dataField="name" width="175" /> <mx:DataGridColumn dataField="address" width="175" /> <mx:DataGridColumn dataField="city" width="100" /> <mx:DataGridColumn dataField="state" width="75" /> <mx:DataGridColumn dataField="zip" width="50" /> </mx:columns> </mx:DataGrid> </mx:Box> </mx:VBox> ... </mx:Application>
You can get more information at http://www.graniteds.org.