flex系列文章

来源:互联网 发布:中文域名管理办法 编辑:程序博客网 时间:2024/05/22 07:05

flex系列文章1:flex漫谈

若干年前,我还在CitiBank工作的时候,全球包括国内IT界就有很多WEB开发者热论Rich Internet思想,众多IT媒体纷纷把RIA当作一个时髦的词汇挂在嘴边,时不时地品评一番。而各网络技术供应商也在暗地里思考着是不是新的机会到来了。当时正是全球java企业级技术开发高速发展的时期,OpenSource运动也在如火如荼的进行着。有一天,头召集我们说Macromedia(当时还没有被Adobe兼并)已经实现了一个实验版本的解决方案,名字叫Flex,让我们关注一下云云,因为下一个项目要用到它。于是从那时起便开始了Flex之旅了。我想我们是Macromedia Flex最早的客户。Flex1.5精彩的UI组件给我留下了深深的印象. 一直到现在都密切的关注着它的发展,用它实现过很多让很多人激动人心的功能,同时也经历着由于Flex先天的不足所带来的困惑,以及每个困惑后面的解决方案。从第一个Beta版本的脆弱到现在2.0的进步,我能明显地感觉到它在成长和发展。我个人也在成长和发展。想以一个过来人的视角告诉国内这些被Flex地魅力所打动地人们一些经验和教训。以及怎么发挥Flex最强悍的地方,巧妙地避开它相对不稳定和脆弱的地方。只要注意足够的细节,我的实践和经验告诉我,Flex总能让人激动人心!

现在,我想首先随便举例介绍一些Flex的短处。从最早的版本1.5开始。

1 不支持鼠标双击事件(解决方案是用两次时间间隔100毫秒的单击事件来模拟),2.0有所改观。

2 Flex的FlashSession和IE的标准HttpSession在Tomcat,WebLogic上部署时没有问题,但是在Unix的JRUN上时,Post请求参数不同步(解决方案是在Remote Object实现中手动同步必要的参数,比如如果你把Spring中的Facade Object作为Remote Object)2.0有同样的问题。在WebService作为Server端的通信方式时没有类似问题。

3  前期绑定和官方声称的不一致。组件绑定在Flex中分为前期绑定和后期绑定。当Client端装载编译过的mxml或as码流时首先出发的事件是Initialize然后是CreationComplete,当我们分别写两个handler相应这两个事件,如果后期绑定可能用到前期绑定时装载的数据,你就会发现数据有可能会没有转载。原因有若干,在下一篇文章中详细介绍。

4 FlashPlayer 9以前的实现,类层次结构太深,实现很机械死板,存在很多重构的可能,比如一个应用中包括若干容器和组件,每个组件的初始化都要调flash类根结点相同的构造器。你配置好Flex自带的Profiler,就可以在Console中看到整个应用在底层的对象创建初始化的所有过程。只要在flex-config.xml中打开开关然后再做些额外的配置就可以了。(解决方案是写一个底层flash类Singleton的类工厂cache所有底层flash类,此方案非常见效,flex并不是想象中的那么慢!)

5 本身没有好的pattern可以直接利用,容易造成mxml和as混在一起,可以选择cairngorm,这个不错。我将单独写一篇关于CAIRNGORM架构的文章。不过2.0时已经被集成进来。

6 CellRender存在数据类型DataType不匹配的Bug等等。

以上说了这些,有些朋友可能有些失望,其实大可不必。我们要保持一种客观的态度,等我把Flex的优点列出来以后,你又会充满信心了,其实,任何产品都有优势和劣势。另外,以上我所列的东西都是我在实际的开发中碰到的,到后来你会发现,等你做完项目必须的优化动作后,反而是数据传输所消耗的时间比Flex组件初始化的时间要长一些,那时可以采用在Server端做个Cache以及Client端Lazy Load的策略。想一下Flex都可以做全球各大股票市场行情实时显示和分析,还有什么它不可以做的呢! 

 

flex系列文章2:精通Adobe Flex --书籍连载

<>

介绍
想写本关于Flex的针对中国开发者的书的想法已经有一年多了,我从2003年就开始关注RIA的解决方案,当时概念炒地很响,但真正很成熟的方案到没有看到多少,直到Flex1.5的发布.RIA概念的提出起源于企业级系统尤其是J2EE架构的实施.最初的设计思想是主要为了提高用户体验,把一部分操作逻辑从服务器端转移到客户端来运行,这也是Rich的含义之一.就我个人的这几年的实际项目开发经验,本人认为Flex开发的核心在于以下几个方面.
1. 首先,能否理解Flex核心类库和运行方式直接决定项目最终的质量,不是说你不能实现功能.既然是富客户,也就是说瘦服务端基本处理一些核心的业务逻辑和事务管理从而为富客户提供数据和会话支持.如果没有仔细考虑Flex表现层的设计,不能为之提供一个合理的数据处理和展现方式的话,最终我们应用程序的执行效率以及可维护和扩展性就很成问题.而这一切的基础就是对Flex核心类库以及运行方式的理解.
2. 能否理解Flex不但能渲染出华丽的页面而且也能和J2EE等企业级开发架构很好的集成,决定你是一般的玩家或者是高级的开发者.我们用Flex展现表现层,仅此而已.它背后的东西还很多,不过这些和Flex倒没有什么必然的关系,如WebService,Spring Remoting Object,ORMaping等等.如果你只能用Flex的标准组件做些可以简单操作的小动画,那么我告诉你你还远远没有入门.
3. 理解一个设计框架如Cairngorm,但并不是说你的任何一个项目都要用它,形而上学总是让人讨厌.Cairngorm的优点是利用几个经典的设计模式如Front Controller,Delegater等在客户端实现了一个局部的MVC,为开发者提供了很好的逻辑隔离,保证了as逻辑的松偶合.但它的优点也是它的缺点,完成一次用户交互从Event Dispatcher到Service Locator需要很多个层次.有点麻烦和死板.所以最好的建议是具体问题具体分析,并不是每个用户动作都一定要走这个过程.
4.深入理解Flex的调试机制,它的Profiler工具对我们最终的系统调试和性能优化很有帮助的.
以上几点只是本人在实际开发过程中总结出来了,仅作参考.Flex最初在2003年由Macromedia公司发布,由于其恶劣的执行效率和开发工具而夭折,直到Flex1.5的发布才真正的商业化,本人也是在这个时候真正的将Flex纳入真实系统的的解决方案中的.我在写这本书的时候,Flex2.0已经发布,并且Flex3 Beta也以发布.Flex2是一个真正的里程碑,依赖其强大的功能和开发工具.Flex已经在RIA解决方案中确立了自己的位置.并且价格也降低了很多.
本人写这本书的目的纯粹是出于个人爱好,同时也是一次很好的知识总结.目前国内没有一本正式的Flex中文书籍,也是本人的动机之一.如果能给广大的Flex爱好者一点点参考算是荣幸之至了.由于本人认识和经验有限,错误和不妥之处敬请批评指正. 书名暂定为<>.本书所有内容将在本人Blog上连载.任何人不经作者同意不许拷贝,盗链,否则后果自负.

本书的读者
本书适合所有Flex的爱好者和开发者.如果你有J2EE等企业开发或者更甚至是Flex1.5的经验那么你会很好很快地理解本书的所有内容.这部分读者可以任意选择你感兴趣的章节来阅读.如果你没有任何的经验,那么我建议你按照本书的章节顺序阅读.因为书中也会分章节详细介绍一些与Flex有关的企业开发的东西.阅读完本书最后一章时,你已经在Flex大道上走很远了.

本书的篇章顺序
第一篇. Flex基础入门
第一章. Flex概念和工具链初步.
第二章. Flex各种开发环境配置初步.
第二篇. Flex核心开发
第三章. Flex标准控件类分析和应用
第四章. Flex标准容器类分析和应用
第五章. Flex效果类分析和应用
第六章. Flex图表组件分析和应用
第七章. Flex数据Model分析和FDS应用
第八章. 自定义Flex组件
第九章. 综合示例
第三篇. ActionScript3.0核心编程
第十章. ActionScript快速入门
第十一章. ActionScript XML编程
第四篇. Flex2.0应用开发和部署
第十二章.  Flex2.0应用开发和部署
第五篇. 高级Flex2.0开发和Cairngorm架构分析和设计
第十三章. 高级Flex2.0开发和Cairngorm架构分析和设计
第十四章. 设计示例
第六篇. Flex2.0企业开发系统集成技术
第十五章. Flex-Ajax集成技术
第十六章. Flex-Spring Remoting Object集成技术
第十七章. Flex-ColdFusion集成技术
附录.第三方类库分析和应用. 

flex系列文章3:flex能做企业级开发吗?我的答案是 当然可以!

    商业RIA的第一个实现是Macromedia Flex1.0在2002年,但终究由于早期FlashPlayer低劣的性能,FlexBuilder的模糊和最弱的debugging功能而夭折。04年1.5的出现虽然依然有很多问题,但真正商业RIA企业级开发的时代已经到来了。那么基于RIA思想的Flex都可以做些什么东西呢? 我的答案:结合当前已经成熟的企业级开发方案如J2EE,.NET等,它最适合做中大规模企业级开发。为什么? 因为Flex的核心价值在表现层,它有基于组件的表现层快速开发模式,结合比较健壮的一些设计架构如Cairngorm,在表现层实现MVC。Cairngorm最终将Flex客户端请求代理到服务器端的Java RemoteObject(FDS数据传输方式的一种). 所以目前比较流行的快速Flex企业级开发的工具集为:

1  Flex + Cairngorm + Spring  + (Hibernate) +  DB

2  Flex + Cairngorm + POJO + (Hibernate) + DB

3  Flex + Cairngorm + (Flex-Ajax Bridge) + DWR + (Struts) + Spring + DB

4  Flex  + WebService + .NET.... 

比如第一种模式是最简单明了的方式,系统配置也很简单只有3个文件(比如1.5时,Cairngorm Delegater 的 Service.xml, flex-config.xml, web.xml)

Service.xml举例代码如



           protocol="http"
       showBusyCursor="true"
       result="event.call.resultHandler( event );"
       fault="event.call.faultHandler( event );">
   

       protocol="http"
       showBusyCursor="true"
       result="event.call.resultHandler( event );"
       fault="event.call.faultHandler( event );">
   

           protocol="http"
       showBusyCursor="true"
       result="event.call.resultHandler( event );"
       fault="event.call.faultHandler( event );">
   

       protocol="http"
       showBusyCursor="true"
       result="event.call.resultHandler( event );"
       fault="event.call.faultHandler( event );">
   

           protocol="http"
       showBusyCursor="true"
       result="event.call.resultHandler( event );"
       fault="event.call.faultHandler( event );">
   

    。。。。。。。

flex-config.xml的关键部分是要注册远程对象,在whitelist标签里,


              com.salesportal.business.CustomerFacade
              stateless-class

至于Web.xml的配置则比较固定。配置做好了,下面举例说明一次完整的用户Transaction。flex代码有


horizontalGap="0" verticalGap="0"  backgroundColor="#92bbd3"
initialize="setSearchMode(ModelLocator.QUICK_SEARCH);
   clientSearchViewHelper.getPreLoadSearchList();
   ">
     
     
  import com.salesportal.model.ModelLocator;

var panelTitle = "Quick Selection";

function setSearchMode(mode) {
  if (mode==ModelLocator.ADVANCED_SEARCH) {
   panelTitle = "Advanced Selection";
   ModelLocator.currentSearchMode = ModelLocator.ADVANCED_SEARCH;
   searchViewTabs.selectedChild=advancedTab
   searchViewStack.selectedChild=advancedSearchView
  } else if (mode==ModelLocator.QUICK_SEARCH) {
   panelTitle = "Priority Clients";
   ModelLocator.currentSearchMode = ModelLocator.QUICK_SEARCH;
   searchViewTabs.selectedChild=quickTab
   searchViewStack.selectedChild=quickSearchView
  }
}

  ]]>
 

...............省略部分代码..................

这个submitHandler相应单击事件click,在ClientSearchViewHelper.as文件里源代码为

public function submitHandler(dataSource:Object, quickSearchGrid1:Object, quickSearchGrid2:Object,quickSearchGrid3:Object,quickSearchGrid4:Object,indusId:Object, compId:Object, prodId:Object): Void
   {
       // reset multiview current page and all pages properties;
       ClientHeatMapViewHelper.lineStart = 1;
       ClientHeatMapViewHelper.currentPage = 1;
       ClientHeatMapViewHelper.allPages = 1;  
       var submitObj : Array = [];
  if(ModelLocator.currentSearchMode!=ModelLocator.QUICK_SEARCH)
  {
  //trace("2");
  submitObj = transferArrayObject(dataSource);
  var dataValidator:Boolean = preFormatParam(submitObj);
  var paramFlag:Number = paramFlagSelector(submitObj);
  if(paramFlag==1)
  {
   //copy all the industries into filterdList and reset the corresponding dataProvider;
   submitObj=appendAdditionalObjs1(dataSource,indusId);
  }else if(paramFlag == 2)
  {
   //copy all the product list into filterdList and reset the corresponding dataProviders;
   submitObj=appendAdditionalObjs2(dataSource,prodId);
  }else if(paramFlag == 3)
  {
   //copy all the product and industries into filterdList,and reset the corresponding dataProvider;
   submitObj=appendAdditionalObjs3(dataSource,indusId,prodId);
  }
        EventBroadcaster.getInstance().broadcastEvent( SalesPortalController.EVENT_CLIENT_SEARCH_SUBMIT, submitObj);
}else
{
  submitObj = quickTransferArrToObj(quickSearchGrid1,quickSearchGrid2,quickSearchGrid3,quickSearchGrid4,ModelLocator.preloadSearchResults);
        EventBroadcaster.getInstance().broadcastEvent( SalesPortalController.EVENT_CLIENT_SEARCH_SUBMIT, submitObj);
}
   }

注意红色字体部分,它是Cairngorm0.99架构中,命令模式的应用。EventBroadcaster广播SalesPortalController.EVENT_CLIENT_SEARCH_SUBMIT事件到Cairngorm的命令分发中心类FrontController

import org.nevis.cairngorm.control.FrontController;
import com.salesportal.commands.*;

class com.salesportal.control.SalesPortalController extends FrontController
{
    public function SalesPortalController()
    {
        initialiseCommands();
}

public function initialiseCommands()
{
   addCommand( SalesPortalController.EVENT_PRE_CLIENT_SEARCH, new PreLoadClientSearchCommand() );
   //addCommand( SalesPortalController.EVENT_CLIENT_SEARCH_SUBMIT, new ClientSearchSubmitCommand() );
   addCommand( SalesPortalController.EVENT_SEARCH_MULTI_SUBMIT,new SearchMultiSubmitCommand() );
   addCommand( SalesPortalController.EVENT_SEARCH_SINGLE_SUBMIT,new SearchSingleSubmitCommand() );
   addCommand( SalesPortalController.EVENT_CLIENT_SEARCH_SUBMIT,new GeneralSubmitCommand() );
   addCommand( SalesPortalController.EVENT_INIT_CALL_REPORT,new CallReportInitCommand() );
   addCommand( SalesPortalController.EVENT_UPDATE_CALL_REPORT,new UpdateCallReportCommand() );
   addCommand( SalesPortalController.EVENT_INSERT_CALL_REPORT,new InsertCallReportCommand() );
   addCommand( SalesPortalController.EVENT_UPDATE_ACTION_PLAN,new UpdateActionPlanCommand() );
   addCommand( SalesPortalController.EVENT_INSERT_ACTION_PLAN,new InsertActionPlanCommand() );
   addCommand( SalesPortalController.EVENT_PROFILE_REPORT_URL,new ProfileReportURLCommand
}

。。。。。。。
public static var EVENT_CLIENT_SEARCH_SUBMIT = "clientSearchSubmit";
public static var EVENT_INIT_CALL_REPORT = "callReportInit";
public static var EVENT_UPDATE_CALL_REPORT = "updateCallReport";
public static var EVENT_INSERT_CALL_REPORT = "insertCallReport";
public static var EVENT_UPDATE_ACTION_PLAN = "updateActionPlan";
public static var EVENT_INSERT_ACTION_PLAN = "insertActionPlan";
public static var EVENT_PROFILE_REPORT_URL = "profileReportURL";
public static var EVENT_ACCOUNT_PLAN_REQUEST = "accountPlanRequest";
public static var EVENT_AC_REPORT_URL_CURYEAR = "acReportUrlCurYear";
}

于是命令类GeneralSubmitCommand来响应事件EVENT_CLIENT_SEARCH_SUBMIT。GeneralSubmitCommand 的逻辑较复杂不便列出源代码,最后GeneralSubmitCommand将事件处理代理到命令类SearchSingleSubmitCommand 里。
class com.salesportal.commands.SearchSingleSubmitCommand implements Command, Responder
{

   public function execute( event:Event ) : Void
   {
      var delegate: SearchSubmitSingleDelegate = new SearchSubmitSingleDelegate( this );    
      delegate.submitFilteredList(event.data);
   }

//-------------------------------------------------------------------------

   public function onResult( event : Object ) : Void
   {     
      //ModelLocator.workflowState = ModelLocator.VIEWING_CLIENT_HEATMAP_SCREEN;
      //ModelLocator.subworkflowState = ModelLocator.VIEWING_CLIENT_SINGLE_HEATMAP_SCREEN;
      ModelLocator.singleReturnedArray = event.result;
   ModelLocator.singleGPName = (ModelLocator.singleReturnedArray[0]).gpName;
      //trace("ModelLocator.singleReturnedArray = event.result==========" + event.result);
   }

//-------------------------------------------------------------------------

   public function onFault( event : Object ) : Void
   {
      ModelLocator.statusMessage = event.fault.faultstring;
      ModelLocator.workflowState = ModelLocator.VIEWING_CLIENT_SEARCH_SCREEN;
      mx.core.Application.alert(ModelLocator.statusMessage+"");
   }
}

红色字体为Cairngorm架构中真正处理用户请求地Delegater以及CallBack回调函数部分。那么到了SearchSubmitSingleDelegate里面的时候,我们可以看出具体的Service Call 了。

import org.nevis.cairngorm.business.Responder;
import org.nevis.cairngorm.business.ServiceLocator;
import mx.utils.Delegate;

class com.salesportal.business.SearchSubmitSingleDelegate
{
   public function SearchSubmitSingleDelegate( responder : Responder )
   {    
      this.service = ServiceLocator.getInstance().getService( "searchSubmitSingleService" );
      this.responder = responder;
   }
//----------------------------------------------------------------------------

   public function submitFilteredList(listObj : Object) : Void
   { 
      var call = service.query4SingleClientView(listObj);
      //trace("Iwasinvocked:====service.query4SingleClientView========IwasInvocked");
      //trace("====================================");
      call.resultHandler = Delegate.create( responder, responder.onResult );
      call.faultHandler = Delegate.create( responder, responder.onFault );      
   }
//----------------------------------------------------------------------------

   private var responder : Responder;
   private var service : Object;
}

红色粗体部分就为对应的java remote object中具体的方法以及回调函数处理器。相应的java代码略。

总结:

j2ee的开发模式很多人都很熟悉,我在此主要总结一下Cairgorm的客户端请求应答方式。

1 首先,客户端广播注册一个自定义事件到frontController(可以随便定义).

  EventBroadcaster.getInstance().broadcastEvent( SalesPortalController.EVENT_CLIENT_SEARCH_SUBMIT, submitObj);

2 FrontController命令类然后把相应的事件分发到相应Command中。

addCommand( SalesPortalController.EVENT_CLIENT_SEARCH_SUBMIT,new GeneralSubmitCommand() );

3。Command然后把具体的业务逻辑代理到不同的Delegater中

var delegate: SearchSubmitSingleDelegate = new SearchSubmitSingleDelegate( this );    

4。Delegater最后做Remote Service Call.

var call = service.query4SingleClientView(listObj);

Caringorm架构中很优秀的用到了不少我们已经非常熟悉的经典设计模式。如Singleton, 工厂,命令,代理,还有桥模式。

谢谢。(代码有点乱不好意思,不过都是本人实际大型项目源代码稍微做了一下改编和省略。另外在Cairngorm2。2中,一些东西发生了变化,有时间再详细讨论)

flex系列文章4:谈谈flex自定义组件

Flex组件开发可分为两种. 一是在mxml中创建自定义组件.另一种则在actionscript class中创建. 总体说来其实大同小异. 首先我们要转换一种观点. mxml组件文件和ActionScript class文件一样都是类.开发者都可以在语法和机制上new 这个类的. 比如两个一模一样的组件.MyButton.mxml 和 MyButton.as. 当我们想要在某一个as函数中动态的创建这个自定义按钮时,都可以.
public var tempButton:MyButton = new MyButton();
parentPanel.addChild(tempButton);
当我们自定义组件时,有若干问题要注意.
1. 如果我们想要给这个新组件添加一个属性,只要在组件类中声明这个变量为public就可以了.
2. 如果想要给这个组件添加一个自定义事件,只要在组件类定义这个Event然后addEventListener就可以了.
3. 如果此组件需要一些Util工具函数,只要在组件定义类内部把这个工具函数public static就可以了.
4. 如果你的组件比较复杂并且存在数据相互依存,那么建议最好将组件的createPolicy设置为"all", default为"auto",
本人写的一个表盘小组件例子.
测试程序源代码为



public function doRunner():void{
//mx.controls.Alert.show(scalertest.currentvalue + '-2');
//scalertest.maxvalue = Number(maxvalue.text);
scalertest.maxvalue = Number(maxvalue.text);
scalertest.zonevalue1 = Number(zonevalue1.text);
scalertest.zonevalue2 = Number(zonevalue2.text);
scalertest.currentvalue = Number(currentvalue.text);
scalertest.startvalue = Number(startvalue.text);
scalertest.duration = Number(duration.text);
scalertest.colorfills = colorfills.text ;
//mx.controls.Alert.show(scalertest.currentvalue + '-2')
scalertest.doInit();
scalertest.runner.end();
scalertest.runner.play();
}
]]>



label="play"
click="doRunner()" x="337" y="41" width="78" height="32"/>
maxvalue="1" currentvalue="0" duration="1" startvalue="0"
zonevalue1="1" zonevalue2="1" colorfills="GYR"
x="84" y="10"/>

















注意红色字体部分!

原创粉丝点击