Thursday, October 26, 2006

My Max Experience in a Nutshell

Overall it was a worthwhile trip. I had a chance to put faces to a lot of the names I've come across in my Flex related web travels, forums and blogs. I was lucky enough to talk Cairngorm with Stephen Brewster, Flex Data Services with Jeff Vroom, ArrayCollections with Matt Chotin, Flex Builder with Mike Morearty, Flex Compiler with Roger Gonsalez. And many more. I was invited by the Adobe flex team to share my experiences and opinions with Flex 2.0

I got wowed by Alex Uhlman's cinematic effects seminar... truly cool stuff. I got a glimpse into the up and coming ModuleManager, now in 2.01 beta, while attending Roger Gonzales' seminar. I met several bigtime Flexcoder forum posters, and chatted on several occasions with Dimitri, getting his take on some of my application architectures. I got to meet the entire Flex team while scarfing down free cookies and I got to talk Framework with the Adobe Consulting guys out of Scotland (Iteration Two folks).

The venue itself was OK. The Venetian is just too big. There were folks staying at the Imperial Palace that could make it from their room to the conference faster than I could from my room at the Venetian. Plus they use this sickly sweet smelling air freshener that starts to permeate through all your clothing and doesn't get any better. The food at the conference was OK as well, however the stupid xylophone they started banging away on at 7:35am to let people know breakfast was ending at 8:00 is something I could have done without. Plus closing it down at 8am was particularly cruel, considering the open bar function at the Palms on Wednesday night. I think that the Palms venue was enjoyed by many but I'm getting old, so the music inside was too loud to talk and the temperature outside dropped quickly.

The content of the sessions and the people that we came to hear speak are what made this conference worthwhile. The vastness of the conference center, the long escalators, the bizarre schedules, the extremely brief exposure to food and drinks during the day, the questionnable venues were all a huge overkill. My advice for next year? Drop the blue man group 15 minute show and take that money to subsidize the ticket price of bringing my wife to the Palms.

My 2 cents.

Thursday, October 19, 2006

Using FDS with Java 5

I found this little tidbit of information tucked away in my documents folder and I figured I might as well share it.

While attending the flex data services course, we were told that both Flex Builder and FDS2 are 100% compatible with Java 5, even though they both ship with the 1.4.xx JRE. Now I'm still looking to wildly expand my Java knowledge, and every book out there is Java 1.5, not to mention certain tools like the Hibernate code generators for Eclipse, optimize their code for Java 5... so I went home that night and decided I was going to make it happen.

First off, I like my Java folder to be close at hand, so I installed the Java 5 jdk first, disabling the automatic installation of the JRE, because the install seemingly won't let me change the install folder of the JRE. Then I installed the JRE seperately. In the end they were located in C:\Java\JDK.. and C:\Java\JRE... respectively.

Then moving on to Flex Data Services (integrated with JRUN edition). In FDS2 I opened the jvm.config file in C:\fds2\jrun4\bin and changed the line java.home=C:/fds2/UninstallerData/jre to java.home=C:/Java/jdk1.5.0_07. I also moved the JRE in uninstallerdata to my desktop to make sure it wasn't being used. I then started FDS2 and it started up fine. (NOTE: originally I pointed it to the JRE, but loading my test .JSP page would fail because it couldn't find com/sun/tools/javac/Main).

Incidentally I also added the JAVA_HOME environment variable to my XP machine and pointed it to the JDK. This takes care of Flex Builder and the Flex SDK as well. (I tested this further in fds and it appears that the jvm.config setting overrides the java_home variable - so if you want to use the java_home for fds2, blank out the value in the jvm.config).

Finally, when you start up the Adobe Flex 2 Command Prompt, it tells you what version of Java it's using. I haven't had any issues at all since upgrading to Java 5. Looks like the instructor was right after all.

Asynchronicity Blues - managing multiple dataservice calls

A while ago I once again came up face to face with the mean and hungry asynchronicity beast that lives within Flex. All I was trying to do was create and save a Contact record programmaticly. The problem was, however, that a Contact has references to ContactType, Country and Division. Simplified, I was trying do the following:

countryService.fill(countries);
contactTypeService.fill(contactTypes);
divisionService.fill(divisions);
contact.country = countries.getItemAt(0); //default
contact.contactType = contactTypes.getItemAt(0); //default
contact.division = divisions.getItemAt(0); //default
contactService.createItem(contact);

Of course, most of the time this would fail because the collections would not yet be filled, being asynchronous in nature. So I had to find a workaround. I created a dataTasks collection and then added a dataTask prior to each fill to the collection. On the result event of each fill I would find my dataTask and remove it from the collection. I added an EventListener to the CHANGE event of my dataTasks collection and when the collection length was back to 0, I would remove the EventListener and create my Contact. This worked fine, however it required alot more coding. Here's what it looked like:
//prior to fill
if(contactTypes.length == 0){
dataTask = new DataTask("ContactTypes",false);
model.dataTasks.addItem(dataTask);}
contactTypeService.fill(contactTypes, "flex:hql", "from ContactType");
//onResult Event
if(dataTask){
model.dataTasks.removeItemAt(model.dataTasks.getItemIndex(dataTask));
dataTask = null;}
//add Eventlistener to dataTasks
model.dataTasks.addEventListener(CollectionEvent.COLLECTION_CHANGE,completeContact);
...
private function completeContact(evt:CollectionEvent = null):void{
if(model.dataTasks.length == 0) {
model.dataTasks.removeEventListener
(CollectionEvent.COLLECTION_CHANGE,completeContact);
var contact:Contact = new Contact();
...
Again, I posted on Flexcoders as to whether or not this was the BEST way to do this, and again Mr. Vroom responded. He wrote that even though the calls on the client are made ASYNCHRONOUSLY, the server handles the requests in SYNCHRONOUS order. In other words I only have to listen for the result event of the LAST fill called. When that result is returned to the client, barring any faults from the previous fills, we can assume that all collections have been filled. Yay! Less Code!

Wednesday, October 18, 2006

Passing Objects on a dataservice fill()

A couple of weeks ago I ran into an interesting issue regarding the passing of a value object as one of the elements in my FillParameters array. Something like this:

dataservice.fill(employees, CompanyVO)
The fill worked fine everytime, however, if I went and added a new employee using:
dataservice.createItem(employee);dataservice.commit();
the collection on my client would not update to show the new employee. Sure I could add the employee by doing an employees.add(employee), and yes, my collection would be updated. However, another employees collection on another client viewing the same records would not.

I turned up the logging level to debug and watched how Flex Data Services was handling the request. It seemed, after a bit of extensive testing, that it did not recognize the CompanyVO object as being the same one across multiple calls. The debugger would even state that no client collections existed with this fill configuration, and therefore none would receive a refresh.

I thought perhaps my CompanyVO.as was perhaps not mapping thru to my CompanyVO.class, so I put my own toString() method in with a quick little output line. Sure enough, FDS was recognizing the object passed as a CompanyVO.class object.

Eventually I gave up. I had played with my java class, trying to override various methods, implementing icomparable, etc. Nothing.

Then Jeff Vroom posted a suggestion on flexcoders. To override the (built-in) in uid property on the Object. First I added some code to have uid equal the companyid, saved my flex project and ran it again:
Company.as
public var uid:String = "";
...
company.uid = company.companyid;

No luck. Then I added the uid property to my java class as well:
private String uid;
public void setUid(String uid)
{ this.uid = uid;}
public String getUid()
{ return this.uid;}

Restarted FDS and ran my flex project again. Problem solved. Here's the steps to reproduce this problem using the crm sample that comes with FDS(as posted by me in the Adobe FDS Forum):

1) in companyapp.mxml, change the fill line in the companyChange function to read:
dsEmployee.fill(employees, company) //change from companyId int to company VO

2) in the crm sample application, addEmployee function, replace this line:
employees.addItem(employee);
with this:
dsEmployee.createItem(employee);
(you don't have to do this if you keep 2 instances of the app running)

3) in the crm.samples.EmployeeAssembler fill method, change the fill by commenting out everything after the line that says return dao.GetEmployees(); and replace them with these lines:
Company cp = (Company) fillParameters.get(0);
return dao.findEmployeesByCompany(cp.getCompanyId());

4) in the crm.samples.EmployeeAssembler createItem method, replace the second dtx.refreshFill with the following line:
dtx.refreshFill("crm.employee", Arrays.asList( new Object[] {newEmployee.getCompany()}));

Wednesday, October 04, 2006

Blog Facelift

As much as like a black background with neon writing, I was getting headaches reading my own material. So I upgraded to the new blogger beta and replaced my template with something a little more conventional. Enjoy.

Monday, October 02, 2006

asp.net C# data webservice for Flex

This is kinda the next evolutionary step from my previous blog. In this example I demonstrate how to get an array of records from .net to flex. Again, I have simplified this for the purpose of this blog. In the asp.net webservice, I would normally retrieve records from the database and then iterate thru the dataset to build my array, while here in this example I have just populated the array manually.

C# Webservice:

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
using System.Xml.Serialization;

namespace Director
{
public class MemberService : System.Web.Services.WebService
{
public MemberService() { InitializeComponent(); }
private IContainer components = null;
private void InitializeComponent() {}
protected override void Dispose( bool disposing ) {
if(disposing && components != null) {
components.Dispose();}
base.Dispose(disposing);
}

[WebMethod]
[XmlInclude(typeof(Member))]
public Member[] getMembers()
{
ArrayList al = new ArrayList();
Member mem = new Member(0,"bob","bsmith","password1",
"bob@abc.com","Initiate",false,DateTime.Now);
al.Add(mem);
Member mem1 = new Member(0,"jim","jsmith","password2",
"jim@abc.com","Member",false,DateTime.Now);
al.Add(mem1);
Member mem2 = new Member(0,"ed","esmith","password3",
"ed@abc.com","Officer",false,DateTime.Now);
al.Add(mem2);
Member mem3 = new Member(0,"neil","nsmith","password4",
"neil@abc.com","Guest",false,DateTime.Now);
al.Add(mem3);
Member[] outArray = (Member[])al.ToArray(typeof(Member));
return outArray;
}
}

[Serializable]
public class Member
{
public int memberid;
public String name;
public string username;
public string password;
public string email;
public string comments;
public bool disabled;
public DateTime created;

public Member(int _memberid, string _name,
string _username, string _password,
string _email, string _comments,
bool _disabled, DateTime _created)
{
memberid = _memberid;
name = _name;
username = _username;
password = _password;
email = _email;
comments = _comments;
disabled = _disabled;
created = _created;
}
public Member(){}
}
}

In Flex, the Member.as file:
package com.abc.ws
{
[Managed]
[RemoteClass(alias="com.abc.ws.Member")]
public class Member
{
public var memberid:int;
public var name:String;
public var username:String;
public var email:String;
public var password:String;
public var comments:String;
public var disabled:Boolean;
public var created:Date;

public function Member(obj:Object = null)
{
if (obj != null)
{
this.memberid = obj.memberid;
this.name = obj.name;
this.comments = obj.comments;
this.username = obj.username;
this.email = obj.email;
this.disabled = obj.disabled;
this.created = obj.created;
this.password = obj.password;
}
}
}
}
In Flex, the application:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Script>
<![CDATA[
import mx.rpc.events.ResultEvent;
import com.abc.ws.Member;
import mx.collections.ArrayCollection;

[Bindable]
private var myMembers:ArrayCollection;

private function resultHandler(event:ResultEvent):void {
myMembers = event.result as ArrayCollection;
for (var i:int=0; i<myMembers.length; i++) {
var currentMember:Member = new Member();
currentMember = new Member(myMembers.getItemAt(i));
myMembers.setItemAt(currentMember, i);
}
}
]]>
</mx:Script>
<mx:WebService id="myService"
wsdl="http://localhost/Director/MemberService.asmx?wsdl"
load="myService.getMembers()"
showBusyCursor="true"
result="resultHandler(event)"/>

<mx:DataGrid dataProvider="{myMembers}"/>
</mx:Application>

asp.net c# webservice consumed by flex

Someone on the flexcoders asked to be provided with a simple example of flex consuming an asp.net webservice. I know this might be VERY simple, but it proves functionality.

ASP.NET Webservice in C#

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;

namespace Director
{
public class Hello : System.Web.Services.WebService
{
public Hello()
{ InitializeComponent(); }

#region Component Designer generated code

private IContainer components = null;
private void InitializeComponent() { }

protected override void Dispose( bool disposing ) {
if(disposing && components != null)
{
components.Dispose();
}
base.Dispose(disposing);
}
#endregion

[WebMethod]
public string HelloWorld(string variable) {
return "Hello " + variable;
}
}
}

Flex Application:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:WebService id="myService"
wsdl="http://localhost/Director/Hello.asmx?WSDL"
load="myService.HelloWorld('Peter')"
showBusyCursor="true"/>

<mx:Label text="{myService.HelloWorld.lastResult}"/>
</mx:Application>

If you're accessing a webservice on another machine or domain, don't forget the crossdomain.xml file in the IIS root:
<?xml version="1.0"?>
<!-- http://www.foo.com/crossdomain.xml -->
<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>

Flex Custom Combobox Component

We needed a country combobox that used an array collection as its dataprovider, however would accept a text value (i.e. "Canada") and select the right item based on the text. We extended the combobox control and embedded our countries array collection inside the control for the purpose of this blog entry. Ultimately we will be using an array collection returned by a dataservice.



Several challenges:
  • searching a specific property of all objects in the arraycollection
  • binding the selectedText property of our control to a function
  • setting the default value of the combobox to nothing

I asked around and it was recommended that I loop through the underlying array of the arraycollection. That sounded very microsoft like and I figured there had to be a better way. It was also suggested I add a dummy record to my countries collection with a "Select Country" string and make that element 0. Again, though this would work, it seemed very ms-ugly.

I struggled with this for quite some time but finally got it working. This custom combobox exposes a property called selectedText. I use ChangeWatcher to keep an eye on the property and trigger a function (setCountry) when it changes. I use a sort on the array collection and the use a cursor on the sort to do my searching... much nicer than a nasty loop. I discovered the handy little "prompt" property which I set to "Select Country".

I'll spare you the gory details of my adventure. Here's the final product:

<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox xmlns:mx="http://www.adobe.com/2006/mxml" initialize="initApp()"
xmlns:util="com.atrexis.util.*" change="selectedText=this.text">
<mx:ArrayCollection id="countries">
<mx:Object code="BDA" description="Bermuda"/>
<mx:Object code="CAN" description="Canada"/>
<mx:Object code="USA" description="United States"/>
<mx:Object code="UK" description="United Kingdom"/>
</mx:ArrayCollection>
<mx:Script>
<![CDATA[
import mx.events.FlexEvent;
import mx.binding.utils.ChangeWatcher;
import mx.collections.*
import mx.collections.ArrayCollection;

[Bindable]
public var selectedText:String;
private var countryCursor:IViewCursor;

private function initApp():void{
mx.binding.utils.ChangeWatcher.watch(this,"selectedText",setCountry);
var sort :Sort = new Sort();
sort.fields = [ new SortField( "description", false ) ];
countries.sort = sort;
countries.refresh();
countryCursor = countries.createCursor();
dataProvider=countries;
labelField="description";
setCountry();
prompt = "Select Country";
}

private function setCountry(event:Event = null):void{
if (selectedText != null && selectedText != "") {
countryCursor.findFirst({description:selectedText});
this.selectedItem=countryCursor.current;
}
}
]]>
</mx:Script>
</mx:ComboBox>

Things to watch out for:

  • the function called by changewatcher must take an event argument
  • remember to update the selectedText property on Change event of combobox