实战 OpenLaszlo 与 db4o

实战 OpenLaszlo 与 db4o
本文于去年年底完成,由于篇幅原因,裁减后提交到《程序员》杂志社,并于2007年2月出版在《程序员》杂志上,文章改名为《开源RIA架构方案-Openlaszlo与db4o》。时隔半年,我把完整的文章提交上来,希望对想要了解 Openlaszlo 的开发者提供帮助。另外,发表在本 blog 上的这篇文章版权归《程序员》杂志社所有。

前言

 

RIA Rich Internet Applications ),这个术语已经出现多年,随着开源社区和厂商的不断贡献,涌现出许多 RIA 产品,几个重要产品分别是:

 

Adobe Flex —— 基于 Flash actionscript MXML

OpenLaszlo —— 基于 Flash JavaScript LZX

       Dojo      —— 基于 JavaScript DHTML XML

       XUL      —— 基于 XUL JavaScript

 

除了以上产品外,还有大量的 Ajax Applet 开源项目。他们的目的只有一个:简化并改善 Web 应用程序的交互操作,使应用程序可以提供更丰富、更具有交互性和响应性的用户体验。

 

本文 UI 方面,我们关注重点是 OpenLaszlo OpenLaszlo 的前身是 LPS Laszlo Presentation Server ),由 Laszlo Systems 公司在 2002 年发布, LPS 是那个时代诞生的第一个 RIA 产品。根据市场和用户的需要, Laszlo Systems 公司于 2004 年在 CPL 协议下发布了 LPS 的开源产品 OpenLaszlo 。目前,代号为“ Legals ”的 OpenLaszlo 即将在明年第一季度发布,它的出现使 OpenLaszlo 能够支持 Ajax DHTML )。 Legals ”的下一个项目代号为“ Osprey ”,它的目标是支持 swf9 的运行目标,也就是当前 Flex 2 所采用的运行文件版本,预计发布时间为明年的第二或者第三季度。 代号为 Orbit 的项目是 Sun Laszlo Systems 公司共同开发的产品 目的是能让 OpenLaszlo 程序运行在包括 Java ME 在内的任何 Java 平台上 比如移动电话、 PDA 、电视机顶盒、打印机 等等。

 

OpenLaszlo 优势在于它是开源的 遵循 CPL 协议 ), 基于开发者熟悉的技术 JavaScript XML 。在 SOLO Standalone OpenLaszlo Output )方式下可部署到任意的 HTTP Web 服务器,在非 SOLO 方式( Proxied 方式)下可部署在装有 OpenLaszlo Server Linux UNIX Windows Mac OS X 操作系统上,并支持任意的 Java Servlet 容器、 Java EE 应用服务器。只要您的浏览器支持 Flash 6 或者更高版本,那么就可以体验 OpenLaszlo 了。

 

本文利用 db4o 作为数据库, db4o 是一个开源的纯面向对象数据库引擎,对于 Java .NET 开发者来说都是一个简单易用的对象持久化工具。同时, db4o 已经被众多的企业级应用验证为具有优秀性能的面向对象数据库。可以通过《 开源面向对象数据库 db4o 之旅: 初识 db4o 》、《 开源面向对象数据库 db4o 之旅: db4o 查询方式 》系列文章来了解。

 

OpenLaszlo 安装

 

Windows 2000/XP 安装为例,首先安装 Java 1.4 或以上版本,接着下载 OpenLaszlo Server 3.3.3 ,这个过程并不困难,安装完毕会自动进入 OpenLaszlo 欢迎页面,很漂亮的世界时钟。

现在就可以立即体验 OpenLaszlo 的魅力了,只要您有任何一款 XML 编辑器就行,哪怕是 Windows 自带的记事本。转到 OpenLaszlo 的默认安装目录“C:\Program Files\OpenLaszlo Server 3.3.3 \Server\lps-3. 3.3” ,新建文件夹“hello”,然后在“hello”文件夹下新建“hello.lzx”文件,向其中写入:

< canvas  width ="200"   >
  
< text > Hello World! </ text >
</ canvas >

第一个程序完成 IE 浏览器输入如下 地址 如图一所示。

                 od1.JPG

                 图一 第一个 laszlo 页面

 

“效果很不错 …… ”,也许您还在欣赏自己地杰作,不过马上感到这样开发不太方便,好在 OpenLaszlo 为我们提供了 Eclipse 插件 IDE4Laszlo IDE4Laszlo 以前是 IBM alphaWorks 项目,现在已经开源给 Eclipse ,可在 这里 找到,目前的版本是 0.2.0 。安装插件之前还必须确保您的 Eclipse 安装了 WTP1.0 SDK

 

接下来安装插件,在 Eclipse 的菜单栏选择“ Select Help >> Software Updates >> Find and Install ”再选中“ Search for new features to install ”,点击“ New Archived Site ”按钮选择刚才下载的插件 zip 文件,最后根据提示即可顺利完成插件安装。

 

上面的 OpenLaszlo Server 依靠 Tomcat 服务器为运行环境,不过 OpenLaszlo 也可以运行在例如 Jboss WebLogic WebSphere 等服务器中,并构建自己的运行环境。本应用程序是在 OpenLaszlo 自己的运行环境中构建,可以在 这里 下载。

程序架构

 

与传统 Web 应用一样,RIA 应用也遵循 MVC 模式,只不过客户端要依赖控制端和服务端提供数据来更新视图,所以多了一个数据层。本实例为了简化程序,使用 JSP 文件来作为控制层与数据层的桥梁,当用户进行操作请求时,OpenLaszlo JSP 发出请求,JSP 获得请求并分析应该调用哪种业务操作,业务层根据所请求的方法与持久层进行交互,将获取的数据以 XML 字符串的形式返回,并通过 JSP 页面传递给 OpenLaszlo 应用。图二展示了本实例的程序架构与数据流转过程。



                             od2.JPG
                                               图二 OLJD架构图

 

下面在 Eclipse 建立一个 Laszlo 工程以展示这个应用的程序结构(由于该应用程序是基于独立的 OpenLaszlo 运行环境,所以未使用 IDE4Laszlo 提供的特性)。

 

打开 Eclipse,创建一个名为“openlaszloWITHdb4o”的 Web 应用程序。接着转入该程序的所在目录,为了能保证该应用程序能顺利加入 RIA 特性,请结合图三进行如下操作:

1、              OpenLaszlo 独立运行环境的 war 包中拷贝“openlaszlo-3.3.3-servlet\lps”目录到“openlaszloWITHdb4o\”下,该目录是 OpenLaszlo 的组件库。

2、              复制“openlaszlo-3.3.3-servlet\WEB-INF\lib”目录到“openlaszloWITHdb4o\WEB-INF\”,该目录是 OpenLaszlo 的支持库,然后在该项目的“Java Build Path”中引入这些库,同时再引入 dom4j db4o 的支持库,应用程序的服务器端会用到。

3、              复制“openlaszlo-3.3.3-servlet\WEB-INF\lps”目录到“openlaszloWITHdb4o\WEB-INF\”,该目录是 OpenLaszlo 的组件配置信息。

4、              在“openlaszloWITHdb4o\src”下,分别创建 bocom 包和类文件,接着创建“helloDb4oUser.lzx”和“peopleoperat.jsp”文件。

5、              最后,把 war 包的 web.xml 文件拷贝过来,启动 OpenLaszlo Server 引擎全靠它了。



                                                         od3.JPG

         图三 eclipse中的工程文件结构


 

服务器端代码

 

应用程序使用了 Java 5 特性,请保证您的开发环境与本文一致。bo 包是整个用户管理应用程序的数据模型,People 类定义了“用户”,包含姓名、电话等基本信息,请 下载程序源码查看

 

com 包是与持久层交互的重要部分,Db4oInit 类是一个 Servlet,它的作用是随 Servlet 容器同时启动 db4o 数据库,并作为 Server 运行,以便在运行时通过 db4o 对象管理工具进行管理,了解 db4o Server/ Client 模式的具体含义,请参考 《开源面向对象数据库 db4o 之旅: db4o 查询方式》 。最后,不要忘了在 web.xml 中加载 Db4oInit Servlet  
 

Db4oInit init 方法,打开一个名为“auto.yap”的 db4o 数据库文件,不用担心,如果是因为第一次运行而没有该文件,db4o 会自动创建,除了数据库文件,还要加上端口号,在这里我们使用 1010,最后进行口令授权。

package  com;

import  javax.servlet.ServletException;
import  javax.servlet.http.HttpServlet;

import  com.db4o.Db4o;
import  com.db4o.ObjectServer;

public   class  Db4oInit  extends  HttpServlet {

    
private  ObjectServer server;

    
public   void  destroy() {
        server.close();
        
super .destroy();
    }

    
public   void  init()  throws  ServletException {
        
this .server  =  Db4o.openServer( " auto.yap " , 1010 );
        
this .server.grantAccess( " admin " , " 123456 " );
    }
}


ContactService 类是 db4o Client 端,用于响应业务操作,包含了最基本的 CRUD 操作。值得注意的是 getPeopleXml 方法,我们利用 db4o QBE 方式查询数据库,如果您愿意,还可以使用 SODANQ 方式查询数据库。之后利用 dom4j 进行对象的序列化,在此笔者还推荐 XStream 结合 java 5 annotation 进行序列化。新增、删除、修改很简单,不再敷述,请下载程序源码查看,注意每次操作完之后 commit 事务即可。

package  com;

import  org.dom4j.Document;
import  org.dom4j.DocumentHelper;
import  org.dom4j.Element;

import  com.db4o.Db4o;
import  com.db4o.ObjectContainer;
import  com.db4o.ObjectSet;

import  bo.People;

import  java.io.IOException;

public   class  ContactService {
    
    
private   static   final  String HOST  =   " localhost " ;   //  server host name
     private   static   final   int  PORT  =   1010 ;             //  server port
     private   static   final  String USER  =   " admin " ;       //  server username
     private   static   final  String PASSWORD  =   " 123456 " //  server password

    
/**
     * retrieve data from db4o as XML String 
     * 
@return  XML String
     
*/
    
public  String getPeopleXml(){
        ObjectContainer db 
=   null ;
        
try  {
            db 
=  Db4o.openClient(HOST,PORT,USER,PASSWORD);
            People peo 
=   new  People();
            ObjectSet
< People >  objectSet  =  db.get(peo);
            Document peplDoc 
=  DocumentHelper.createDocument();
            peplDoc.setXMLEncoding(
" UTF-8 " );
            Element rootNode 
=  peplDoc.addElement( " root " );
            
for ( int  i = 0 ;i < objectSet.size();i ++ ){
                Element peplNode 
=  rootNode.addElement( " node " );
                People eachPeople 
=  (People)objectSet.get(i);
                peplNode.addAttribute(
" userId " ,String.valueOf(eachPeople.getUserId()));
                peplNode.addAttribute(
" userName " ,eachPeople.getUserName());
                peplNode.addAttribute(
" userPhone " ,eachPeople.getUserPhone());
                peplNode.addAttribute(
" userEmail " ,eachPeople.getUserEmail());
                peplNode.addAttribute(
" userAddress " ,eachPeople.getUserAddress());
            }
            
return  peplDoc.getRootElement().asXML();
        }
catch (IOException e){
            e.printStackTrace();
            
return   " <error/> " ;
        }
finally {
            
if (db != null ){
                db.close();        
            }
        }
    }
}

peopleoperat.jsp 代码也相对简单,请 下载程序源码查看

 

页面代码

 

要编写 OpenLaszlo 服务器 能运行的代码,就必须使用 LZX 语言,LZX 是一种面向对象的基于标记的语言 利用 XML JavaScript 语法构建 RIA 表现层 通常这些编写完成的代码会经过 OpenLaszlo 编译器进行编译,最后可作为独立的 swf 文件或部署在服务器上运行。 LZX 语言使用开发者熟悉的语法和命名方式,因此能较容易地融入现有编程环境。

 

LZX 语言具有继承、封装、多态等面向对象语言所具有的特性。通常,一个标签就等于实例化了一个类,比如 <view> 就是 LzView 的实例。值得注意的是,和基于 DHTML/JavaScript 的程序有着概念上的不同,典型的 DHTML/JavaScript 程序是直接由浏览器解析、执行嵌入其中的 JavaScript;而 LZX 中的 JavaScript 则是在 OpenLaszlo 服务器端编译成客户端 Flash 解析引擎能运行的字节码,再下载到浏览器执行的(swf 文件 )。

LZX 程序是运行在名为 canvas 的可视对象基础上的,也就是以 <canvas> 开始并以 </canvas> 结束的标签中。在 canvas 中,有许多 view,这些 view 嵌套着业务逻辑、可视化特性、可编程属性、尺寸、位置、背景颜色、不透明性、可点击性、伸缩性,等等信息。view 也可以包含资源,例如图像或视频,也可以动态绑定任意 XML 格式数据集。

现在我们就进入代码部分,首先观察前三行:

< canvas  debug ="true"  height ="580"  width ="780"  title ="Hello db4o, openlaszlo"  fontsize ="12" >
    
< debug  x ="0"  y ="400"  width ="300"  height ="200" />
    
< splash />

第一行代码向我们表明了 LZX 语言的面向对象特性,canvas 对象的 debug 属性为了让程序调试时能在 debugger 窗口打印出调试信息,heightwidth 属性分别指定了 canvas 对象的高和宽,超过这一大小,将不会被显示,title 属性和 HTML title 标签功能一样,fontsize 属性作为一个全局参数,如果没有明确指定,那么在 canvas 中所有的 view 都将使用该字体大小。第二行代码实例化 Debug 对象,并规定了 debugger 的高和宽,除此以外,还定义了窗口出现的 x(横坐标)、y(纵坐标),这些都是相对坐标。第三行定义了 splash 对象,该对象是加载应用程序时的进度条。

 

LZX 还提供了强大的绘图功能,下面就让它小试牛刀吧:

     < drawview  name ="banner"  width ="780"  height ="50" >
        
< handler  name ="oninit" >                               
            this.beginPath();
            this.lineTo(780, 0);
            this.lineTo(780, 50);
            this.lineTo(0, 50);
            this.lineTo(0, 0);
            this.closePath();
            var g = this.createLinearGradient(0,0,0,50);
            this.globalAlpha = 0;
            g.addColorStop(0, 0x666666);
            this.globalAlpha = 1;
            g.addColorStop(1, 0xFFFF00);
            this.fillStyle = g;
            this.fill();       
        
</ handler >
    
</ drawview >

在本应用中,我们要实现的是一个渐变的黄色 bannerdrawview 类是专门绘图的,handler 类捕获了 oninit 内置事件。我们这样设想:手中有只画笔,从某点开始,然后按照一定路径进行边框描绘,最后填充颜色。同理,this.beginPath() 设置画笔的开始点(0,0 坐标),然后绘制直线到 780,0 坐标,接着再绘制到 780, 50 坐标,然后闭合方框(this 表示 drawview 自身对象实例),this.createLinearGradient(0,0,0,50) 方法是在按照 x0, y0, x1, y1 这种路径进行梯度过渡,this.globalAlphag.addColorStop() 是一系列的颜色填充设置,从 0x666666 颜色均匀过渡到 0xFFFF00 颜色,最后 this.fillStyle = g 应用该设置,this.fill() 填充即可。

 

有了以上基础,我们开始解析可视化组件,下面是构造边框为绿色的 bordergreenbox 类:

     < class  name ="bordergreenbox"  extends ="view"  x ="1"  y ="145"  width ="${canvas.width-2}"  bgcolor ="0x9ae900" >
        
< view  name ="innerbox"  bgcolor ="0xf4f4f4"  x ="1"  y ="1"  height ="${parent.height-2}"  width ="${parent.width-2}" />
    
</ class >

class 标签是自定义类的基础,name="bordergreenbox" 属性为类命名,extends="view" 声明 “bordergreenbox”类是继承自“view”(view 是最常用的类,用于组织包含在其中的元素的交互),相应的也就继承了“view”所有的属性和方法。定义宽度时用到了“${}”约束符,这意味着“bordergreenbox”的宽度要受到“canvas.width-2”的约束,接着设置背景为绿色 bgcolor="0x9ae900"。为了能达到绿色边框的效果,我们在其中嵌入另一个 view,再设置颜色、相对坐标、高、宽。

 

以下部分是在构造 newPeople 类,用于让用户输入基本信息:

     < class  name ="newPeople"  extends ="view"  fontsize ="13"  visible ="false" >
        
< simplelayout  spacing ="4"  axis ="x" />
        
< text > 姓名: </ text >< edittext  name ="userName"  width ="60" />
        
< text > 电话: </ text >< edittext   name ="userPhone"  width ="100" />
        
< text > 电子邮件: </ text >< edittext   name ="userEmail"  width ="140" />
        
< text > 地址: </ text >< edittext  name ="userAddress"  width ="230" />
    
</ class >

bordergreenbox 类不同的是,newPeople 类引入了新属性 visible="false",它的作用是让newPeople 不可见,如果不设置该属性则默认为可见。我们还引入了 simplelayout 类,它的作用是均匀分布 newPeople 类中所有的对象,spacing="4" 是在定义间距为 4axis="x" 定义按照横向分布。text 类是用来嵌入文字的,且支持嵌入 <a><b><font> HTML 标记,而 edittext 的作用则类似 HTML 中的 input 标签。运行效果如图四。

                 od4.JPG

                                        图四:newPeople

 

如何访问 JSP 提供的数据源呢?在这个应用中,分别构建了两个数据集,db4odata 用来获取返回的列表数据,peopleoperate 进行业务操作,下面主要分析 db4odata

     < dataset  name ="db4odata"  src ="peopleoperat.jsp?action=getPeopleData"  type ="http"  request ="false" />
    
< datapointer  name ="db4odtpt"  xpath ="db4odata:/*" >
        
< handler  name ="ondata" >
            canvas.bgview.rowcontainer.datapath.setPointer(this.p); 
            canvas.loader.setVisible(false);
            Debug.write("data loaded!");
        
</ handler >
    
</ datapointer >

dataset 是数据访问类,src 作为资源访问路径指向 JSP 页面,并规定type http 协议,当request="false" 时,必须通过调用 doRequest() 方法才能访问资源并获取数据,而为“true”时,则会自动访问资源,一般应设置为“false”。接下来用到了 datapointer 类,datapointer dataset 的数据指针,并遵循 XPATH 语法规范访问数据,xpath="db4odata:/*" 定义 XPATH 的路径,既 db4odata dataset 的所有子路径。handler 类捕获 ondata 事件(ondata datapointer 对象内置事件),并通过“.”运算符为 bordergreenbox 类的实例 bgview 设置数据源,再隐藏一个名为 loader 的对象,代码如下:

< text  name ="loader"  x ="2"  y ="2"  text ="loading" />



不错,loader 对象仅仅写下“loading...”,这是为了改善程序的互动效果。接下来分析 bordergreenbox 类实例 bgviewbgview 包含了两个 view 对象。以下代码向我们展示了第一个 view,它的作用是提供列名:

         < view  x ="1"  y ="15"  fontsize ="14"  fgcolor ="0xFFFFFF"  fontstyle ="bold"  width ="${canvas.width-4}"  height ="25"  bgcolor ="0xdd8910" >
            
< text  x ="40" > ID </ text >
            
< text  x ="140" > 姓名 </ text >
            
< text  x ="260" > 电话 </ text >
            
< text  x ="410" > 电子邮件 </ text >
            
< text  x ="620" > 地址 </ text >       
        
</ view >



第二个 view 对象 rowcontainer,刚才我们分析 db4odata 数据源集时捕获的 ondata 事件吗?不错,一旦接收到数据,rowcontainer 也就从 name="db4odtpt" 的数据指针中获取了数据:

 

         < view  name ="rowcontainer"  x ="1"  y ="39"  datapath =""  width ="$once{parent.width-2}"  height ="$once{parent.height-40}"  clip ="true" >
            
< view  name ="columns"  datapath ="${parent.datapath}"  width ="$once{parent.width-2}" >
                
< simplelayout  axis ="y"  spacing ="1" />
                
< selectionmanager  name ="selector"  toggle ="true" />
                
< view  datapath ="*"  width ="$once{parent.width}"  bgcolor ="0xffffff"  onclick ="parent.selector.select(this);" >
                    
< text  x ="0"  datapath ="@userId"   width ="110" />
                    
< text  x ="115"  multiline ="true"  datapath ="@userName"  width ="100" />
                    
< text  x ="220"  multiline ="true"  datapath ="@userPhone"  width ="130" />
                    
< text  x ="355"  multiline ="true"  datapath ="@userEmail"  width ="180" />
                    
< text  x ="540"  multiline ="true"  datapath ="@userAddress"  width ="220" />
                    
< method  name ="setSelected"  args ="amselected" > <![CDATA[       
                        if (amselected) {         
                            var bgcolor = 0xD5E4F3;
                            canvas.np.sendit.userid = this.datapath.xpathQuery('@userId'); 
                            canvas.np.userName.setText(this.datapath.xpathQuery('@userName'));
                            canvas.np.userPhone.setText(this.datapath.xpathQuery('@userPhone'));
                            canvas.np.userEmail.setText(this.datapath.xpathQuery('@userEmail'));
                            canvas.np.userAddress.setText(this.datapath.xpathQuery('@userAddress'));
                            canvas.np.userName.setAttribute('enabled',false);   
                        } else {    
                            var bgcolor = 0xFFFFFF;
                            canvas.np.userName.clearText();
                            canvas.np.userPhone.clearText();
                            canvas.np.userEmail.clearText();
                            canvas.np.userAddress.clearText();
                            canvas.np.userName.setAttribute('enabled',true);
                        }
                        this.setBGColor( bgcolor ); 
                    
]]> </ method >  
                
</ view >
            
</ view >
        
< scrollbar />
        
</ view >



clip="true"
属性是为了让包含在其中的子对象之间能够很好的布局。rowcontainer 中又嵌套了 view 对象 columns,并通过父对象取得了数据路径 datapath,接着按纵向均匀布局其中的行,selectionmanager 类似 HTML 中的 select 标签,一旦选中某行将触发为其内置属性 selected 赋值为“true”。接下来的 view 才是真正显示数据列表,onclick 事件也是 view 类的内置事件,当点击了某条记录,将联合 selectionmanager 进行操作,所以在这里为 selectionmanager 传入了选中的某条记录。datapath="@userId" 用于绑定 XML 数据中的 userId 属性,并自动显示。刚才说到由于 selectionmanager view 是紧密联系的,所以在这里重写了 view setSelected 抽象方法,并传入了布尔类型数据。在 <![CDATA[ ]]> 之间的代码,OpenLaszlo 将忽略其中的特殊字符(例如“<”符号),np 是之前我们构造的 newPeople 类的实例,接下来会讲到,而 canvas.np.sendit.userid = this.datapath.xpathQuery('@userId') 则是在进行赋值操作,虽然 newPeople 类的实例是不可见的,但却隐含的传入了数据,canvas.np.userName.setAttribute('enabled',false) 是动态创建的属性,为了区分“新增”、“修改”操作(新增可编辑“用户名”而修改则不能)。var bgcolor 是在声明局部变量,和 Java 语言不同的是,if 语句内声明的局部变量也能在语句之外调用,所以 this.setBGColor(bgcolor) 不会出现任何异常。

 

想必大家很关心 newPeople 类实例 np 是什么样子,接下来会讲解到如何添加和修改数据并提交给 JSP 处理:

     < newPeople  name ="np"  y ="85" >
        
< button  name ="sendit"  height ="24"  width ="40"  text ="提交"   >
            
< attribute  name ="userid"  value ="0" />
            
< attribute  name ="action"  value ="null" />
            
< handler  name ="onclick" > <![CDATA[
                var username = parent.userName.getText();
                var usermobile = parent.userPhone.getText();
                var useremail = parent.userEmail.getText();
                var useraddress = parent.userAddress.getText();
                var usernamenabled = parent.userName.getAttribute('enabled');
                function send(action){
                    peopleoperate.setQueryParam('action',action);
                    peopleoperate.setQueryParam('userId',userid);
                    peopleoperate.setQueryParam('userName',username);
                    peopleoperate.setQueryParam('userPhone',usermobile);
                    peopleoperate.setQueryParam('userAddress',useraddress);
                    peopleoperate.setQueryParam('userEmail',useremail);
                    peopleoperate.doRequest();
                    canvas.loader.setVisible(true);
                    parent.setVisible(false);
                    canvas.bgview.setY(85);
                }
                if (username!='' && usermobile!='' && useremail!='' && useraddress!=''){
                    if (action == 'add' && usernamenabled == true){
                        Debug.write('addPeople');
                        send('addPeople');
                    } else if (action=='update'){
                        Debug.write('updatePeople');
                        send('updatePeople');
                    }
                }        
                userid = 0;    
            
]]> </ handler >
        
</ button >
    
</ newPeople >

在此我们引入了 button 类,接着构造了两个属性,userid 用于在修改时传递用户 ID,而 action 则用于确定是何种业务操作。接着捕获了 button 类的 onclick 事件,声明局部变量以便接下来发送,function send(action){…} 是构造的内部方法,用于发送请求,peopleoperate.setQueryParam() 是在对 peopleoperate 数据集创建请求参数,最后 peopleoperate.doRequest() 发出请求。构造好内部方法后,开始判断是哪种业务操作,并分别传入 'addPeople' 'updatePeople' 参数,待提交完成后 parent.setVisible(false) 隐藏 np

 

我们在前面提到了当点击了 columns 对象中的某一行将自动为 np 对象填入值,而这个时候 np 是隐藏的,不错,需要增加一个按钮来显示 np 以便进行修改:

     < button  name ="updtuser"  x ="60"  y ="55"  height ="24"  width ="40"  text ="修改" >
        
< handler  name ="onclick" >
            parent.np.setVisible(true);
            parent.bgview.setY(115);
            canvas.np.sendit.action = 'update';
        
</ handler >
    
</ button >

新增和修改的操作差不多,而删除则传入用户 IDpeopleoperate.setQueryParam('action','delPeople') 直接删除。

 

到此,页面代码也完成得差不多了,启动您的服务器。运行效果如图五:

                  od5.PNG

             图五: openlaszloWITHdb4o 应用 程序

 

Tips

 

在开发 OpenLaszlo 过程中会遇到不少意想不到的问题,最常见的是提交数据后 debugger 打印出“data conversion error……”信息,检查程序后未发现任何异常,很让人困惑,其实你只需在服务器端返回一个简单的 XML 即可,哪怕是“<ok/>”标签。

 

您的应用程序在运行时是可以随时点击鼠标右键查看源代码的,如图六:



                                                                od6.JPG

                  图六:程序属性

 

这对安全构成了威胁,特别是部署后实际运行的应用。OpenLaszlo 为我们考虑到了这点,只需修改被部署的应用的“WEB-INF\lps\config\lps.properties”文件,把“allowRequestSOURCE”参数设置为“false”即可。

 

提高 OpenLaszlo 开发效率原则:

 

1.    只做自己擅长的事

OpenLaszlo 只擅长做表现层,不要奢求在界面上处理一些复杂逻辑,这些要交给后台处理。也不要用它来做类似于博客等以文字为主的应用,体现不出优势,反而体现出劣势。

2.    做好自己该做的事

类似于客户端数据校验的功能,前台该做好的,就不要交给后台去判断数据是否有效,前台每次提交都要保证自己是没有问题的,和后台的结合应该是无障碍的。

3.    不要过分追求效果

不要为了 RIA RIA,界面的过多动态效果,只会让用户眼花缭乱,而且加大了代码量,增加了性能问题,损害了用户体验,只有简单中有一点新奇、静中有动才是境界。

 

提高 OpenLaszlo 运行效率原则:

 

1.    dataset 的请求(request)属性永远是 false

只有在事件中才进行 doRequest()

2.    尽量少用约束符${}来指定视图间的位置关系

比如:<view name="myview" x="${parent.b.x}" .../> 因为程序在初始化过程中要评估表达式的值,而且生成约束 constraints,这样对性能产生很大影响。

3.    需要数据来生成视图内容的组件尽量不用严格数据绑定

这样做是为了减少程序编译时间合减少服务期端传送过来的文件尺寸。正确的做法是将组件的 datapath 置为空,即定义组件的 datapath="",而在用户事件或者在 datapointer 中的 ondata 事件中用 component.datapath.setPointer(this.p) 中进行运行时绑定。

4.    通过事件给组件填充数据

combobox list 等组件的数据只在需要时填充,通过打开窗口或者点击按钮的事件来为这些组件填充内容。只要填充了一次,下次如果数据没变,就不会再重复生成视图。

5.    使用 initstage="defer"

OpenLaszlo 官方文档推荐使用 initstage="defer" 来阻止某些不是马上用到的视图的生成,而在需要的时候才 complete,但笔者试过,不太好掌握时机,而且会让程序变的互相耦合,效果不甚理想。

6.    使用 dataoption="lazy"

list 组件有一个很有用的属性:dataoption="lazy",这个属性可以使列表中的数据更新起来非常迅速,尤其是在大数据量的情况下。它的原理在于把部分视图进行了缓存,而不是全部销毁和新建,可惜 tree 组件对于这个属性不管用。

7.    尽量降低视图的层次

尽可能的减少视图的嵌套层次,能直接放在 canvas 下,就不要放在其他视图里面,而且window modaldailog 应该严格的直接放在 canvas 里面。这样做是为了加快程序的渲染速度,并且简化编程时路径引用,方便查看代码结构。

8.    尽量少用 state/animator

用这些会增加内存占用和代码量,而用 node animate 方法却可以轻松的控制视图运动,而且没有额外的内存占用。

9.    控制单个 canvas 文件的代码规模

一个 canvas 文件代码行数最好控制在 1000 行以内,其中 include 进来的方法、资源、自定义组件的代码数目不算在内,这样做是为了获得较快的界面初始化时间,和方便的代码维护。

10. 尽量采用 SOLO 方式部署

如果不需要 RPC 等特殊功能,建议采用 SOLO 方式部署应用,将已经写好的 OpenLaszlo应用编译成 swf 文件,在 html 文件中包裹,这样可避免用户第一次请求 lzx 文件的长时间等待,也避免了服务期重起后重新编译 lzx 文件,还可以减少 OpenLaszlo Server 部署时对服务器空间的占用(尽管才十几兆)

11. 模块化应用

将具有独立功能的应用模块单独分离出来,即用一个包含 canvas lzx 文件配合其他组件来实现一个小模块,各个模块之间导航用 loadURL() 来完成,各个模块共享 Session 以及后台数据,实现模块之间的通讯。

 

结论

 

通过上面的介绍,想必您已经体会到 OpenLaszlo db4o 结合起来做程序是多么的有趣。如果在不借助第三方框架,用传统的 JSP+JavaScript JDBC 技术开发,相信其代码量会远远超过前者。OpenLaszlo db4o 的设计目标都是为节约开发周期而努力,相信他们也做到了,剩下的就看开发者是如何利用其优势的,您准备好了吗?

 

 

关于作者

 

Rosen Jiang 来自成都,是 db4o OO 的忠实 fansRIA 倡导者,是 2007 db4o dvp 获得者之一。你可以通过 [email protected] 和他联系。

 

Lwz7512 来自北京 ,是一名 OpenLaszlo Developer,他 OpenLaszlo 在中国的User Group负责人,也是Openlaszlo中国开发者社区创办者, 你可以通过 [email protected] 和他联系。

 

参考资料

 

资源

OpenLaszlo 官方网站 http://www.openlaszlo.org/

db4o 官方网站 http://www.db4o.com/

Openlaszlo 中国开发者社区 http://www.openria.cn/

中国RIA开发者论坛 http://www.riachina.com/

 

学习

Rich Internet Applications 的技术选项: http://www-128.ibm.com/developerworks/cn/web/wa-richiapp/index.html

Rich Internet Applications and AJAX - Selecting the best product

http://www.javalobby.org/articles/ajax-ria-overview

Introducing OpenLaszlo

http://www.xml.com/pub/a/2006/10/11/introducing-open-laszlo.html

OpenLaszlo 文档中心

http://www.openlaszlo.org/documentation

OpenLaszlo 应用程序中使用 Apache Derby,第 1 部分:使用 Derby 提供数据

http://www-128.ibm.com/developerworks/cn/views/opensource/tutorials.jsp?cv_doc_id=110109

OpenLaszlo 应用程序中使用 Apache Derby,第 2 部分:存储和嵌入数据

http://www-128.ibm.com/developerworks/cn/views/opensource/tutorials.jsp?cv_doc_id=110110

使用 OpenLaszlo 创建 Web 富客户端

http://www-128.ibm.com/developerworks/cn/xml/wa-openlaszlo/index.html

Openlaszlo 开发规范

http://www.openria.cn/posts/list/49.page

面向对象数据库 db4o 之旅,第 1 部分:初识 db4o http://www-128.ibm.com/developerworks/cn/java/j-lo-db4o1

面向对象数据库 db4o 之旅,第 2 部分:db4o 查询方式http://www-128.ibm.com/developerworks/cn/java/j-lo-db4o2



《程序员》杂志版权所有!引用、转贴本文应注明本文来自《程序员》杂志。

你可能感兴趣的:(实战 OpenLaszlo 与 db4o)