综合使用JavaScript、LotusScript Agent和Formula的技巧1

来源:互联网 发布:java调用shell脚本 编辑:程序博客网 时间:2024/04/29 23:00

综合使用JavaScript、LotusScript Agent和Formula的技巧

一 概述
在使用 Designer开发B/S模式的应用时,JavaScript、LotusScript和Formula是我们主要用到的三种开发语言。它们在各自的位置都有着很强劲的优势。
1. JavaScript因为只能取得浏览器端的数据,不能访问Notes DOM;所以,主要用在浏览器端的数据验证、信息提示等对当前Brows窗口操作易的用性功能。

2. LotusScript能访问Notes DOM。在Notes客户端可以取得当前文档的数据,但是,因为其无法直接在浏览器端工作;所以在B/S模式的应用中LotusScript只能在服务器端工作,我们通过编写LotusScript代理来实现其强大的文档处理能力。

3. Formula能访问后台数据,语言简洁,数据处理能力较弱,不能在浏览器端工作。Formula主要使用在元素的显示控制以及域值的计算公式及简单的数据处理方面。
这三者使用的范围和处理能力各不相同。在应用中将其三者综合起来使用,会使应用的各方面功能大大增强。

二 由JavaScript向代理(LS/FL)通信

一般情况下JavaScript向代理的通信,使用url command。即如下格式:http://Host/Database/AgentName?OpenAgent&参数。这类操作可以用来解决数据查询、文档删除等任务。这类操作的特点是可以带参数,但是取不到浏览器端的当前文档,无法对浏览器端的文档进行处理。
那么,为什么要使用JavaScript向代理通信,并让代理取得浏览器端的当前文档呢?先看一个例子:
在某申请系统中,用户创建新申请可以这样做:打开新的申请单,填写各项目后,点击操作按钮“提交申请”,就完成了操作。对设计者而言触发“提交”操作必定运行一个代理或一段公式(此处我们先假定提交操作运行的是代理,后面将介绍提交操作运行公式的情况)。在真正运行代理的“提交”之前,系统一定要进行数据合法性的验证。最简单的做法是在每个域里面写入验证公式,或者在代理里写入验证数据合法性的代码。但是,这类做法的共同的缺点是:即使数据不合法,服务器端和浏览器端也发生了交互,占用带宽、影响网络速度。并且,出错提示信息只能以网页形式输出在原窗口上。这样,就会使原窗口中的信息丢失,不方便用户修改原来的数据。
针对这种情况,我们想让域的验证工作在浏览器端执行。并且,出错提示使用弹出窗口。JavaScript正能符合要求,但是难点就在:一旦验证通过怎样调用代理?
解决的办法是:
1. 保持“提交申请”操作按钮和提交代理不变。在浏览器端预览表单,点击右键查看源文件,找出“提交申请”操作按钮的onclick事件中的代码。

2. 新建一个同样名为“提交申请”操作按钮,将原来的“提交申请”操作按钮隐藏。在新建的操作按钮中写入域验证信息JavaScript代码,如果验证通过则执行第一步的代码。如果不通过则使用alert弹出窗口,提示出错信息。
3. 注意:每增减一次操作按钮,都会影响原“提交申请”操作按钮的onclick事件代码。所以,比较好的办法是将原来的“提交申请”操作按钮作成共享操作。

以上做法是我们在工作流模版里使用的方法。现在,我们有一种改进方法:
1。不用原来的操作按钮,把触发提交代理的写在一个按钮(button)里,给该按钮命名(假设为“b1”,在其属性框中找到html选项卡“name”栏中填为“b1”),用css将该按钮隐藏。(同样在tml选项卡中,在“style”栏填入“visibility: hidden”)

2。新建一个名为“提交申请”的操作按钮,在按钮中写入域验证信息JavaScript代码,如果验证通过则执行“ document.forms[0].b1.onclick()”(此处的b1是上一步我们假设的按钮name)。如果不通过则使用alert弹出窗口,提示出错信息。

3。注意如果有多个这种操作在同一个表单里,那么按钮的name必须不同。对按钮的调用显然也应该根据其name。

纵观两种用JavaScript调用代理的方法,各有特点。使用urlcommand比较方便易用,但缺点是取不到浏览器端的当前文档,代理很多强大的功能无法展开。使用第二种方法,实际上是JavaScript通过调用操作按钮,间接调用了代理。这种方法功能强大,但是,创建和维护都稍显复杂。最后指出:所谓使用JavaScript调用Formula,也就是指用JavaScript调用操作按钮,而操作按钮则中执行的是各类对文档进行操作的Formula。即用JavaScript间接调用了Formula。
三 由代理向JavaScript通信

使用代理向JavaScript通信,可以分为用代理写出JavaScript程序和用代理设置JavaScript参数。
[一]用代理写出JavaScript程序。
我们可以使用代理中的print系统函数,在窗口打印信息。这些信息为浏览器认做HTML语句来解释。这样我们就可以用print函数在代理中写JavaScript程序。
例如以下代码片段:
If dc.count=0 then
‘dc为在前面程序里使用database.search得到的DocumentCollection
Print "<SCRIPT LANGUAGE=JavaScript>"
Print "alert(""没有找到相关文档,请和管理员联系。"")"
Print "location.href = ""/Web+test.nsf/Main+View?OpenView"""
Print "</SCRIPT>"
End if
该段代码执行如下功能,判断如果当前搜索得到的文档集为空,就在浏览器端显示出错信息,然后将窗口路径指向某视图。

再看一个代码片段:

flag =curdoc.save(True,False)
If flag Then
Print "<OBJECT id=closes type=""application/x-oleobject""classid=""clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11"">"
Print " <param name=""Command"" value=""Close""> </object>"
Print "<script>"
Print "window.setTimeout('closes.Click()',1000)"
Print "</script>"
Print "<h4>个人选项保存成功。</h4>"
End If
该段代码执行如下功能,判断如果当前文档保存成功,就在浏览器端显示确认信息,随即关闭窗口。

使用代理写JavaScript程序我们可以做出许多有效、方便的功能来。比如动态的信息展播等。

[二]用代理设置JavaScript参数。
这里JavaScript是表单中写好的,但是JavaScript的某些参数可能使用表单中某些域的值。这样用代理设置了该域的值也就设置了JavaScript参数。
例如:某系统用户要求在申请单中的“编号”字段用户能手动填写,但是又要能保证:如有重复的号码,系统要给与提醒。
解决办法就是:
1. 使用webquaryopen事件运行代理,使其将所有已经编号文挡的编号值都以逗号隔开,放入文档的某域中。
2. 而在表单内则写入JavaScript代码。将该域的值取出作参数,再将该参数以逗号隔开取得字符串数组。
3. 最后在域有效性验证时,比较当前的编号是否存在于数组中,若是则返回出错,否则成功。

比较以上介绍的两种方法。第一种方法:因为JS代码都由代理写出来,所以形式更灵活、功能更强大。但是,由于代码是Print出来的,无法和文档在窗口共存;所以,这种方法一般使用在信息展示方面。第二种方法:由于大部分代码是在浏览器端写好的,所以功能相对单一。因为代理通过修改域值影响JavaScript代码;所以,一般用在文档处理方面。
四 由Formula向JavaScript通信

Formula调用JavaScript。与代理调用JavaScript的第2条十分相似。但是,因为Formula能控制表单元素的显示属性。而Agent能访问更多的数据,所以Formula与Agent各有千秋。
这里介绍一个比较有趣的例子,就是怎样使文挡按条件自动编辑。即某些情况下文挡打开时为阅读状态。另一些情况,文档打开时则为编辑状态。显然,设置表单的自动编辑功能是无法完成功能的。
做法:
1. 在表单建立JavaScript函数autoedit。

2. 函数中写入代码取得当前窗口路径,将路径最后几个字符opendocument替换成editdocument,然后存在变量mypath中。
3. 假设文档是否自动编辑的判断条件是当前用户在公共通讯录里的个人文档的“myauto”字段是否为“yes”。那么在JavaScript函数“autoedit”中写JavaScript代码:t=<此处为计算文本>,计算文本的公式为“@dblookup(""; "" : "names.nsf" ; "people"; @name([ cn ] ; @username) ; "myauto")”。
4. 在JavaScript函数“autoedit”中加入代码,判断t是否为“yes”,若是则为将window.location置为mypath。然后将本段代码设置为在编辑状态下隐藏。
5. form的onload事件中加入JavaScript代码,调用autoload函数。

注:以上文档是我在学校里时写的,现在看来有些观点难免有点牵强,实例也不够生动;贴上来权作抛砖引玉。
程序开发总结之二
 
我如何在页面上建立一个热点,让它打开一个文档?
答: 在页面上写一段文字,然后选上这段文字,然后点菜单”创建” –热点--操作热 点.然后选LotusScript ,举个例子,比如打开ID 为NT00000C62的文档:
Sub Click(Source As Button)
Dim uiworkspace As New notesuiworkspace
Dim curdatabase As notesuidatabase
Dim database As notesdatabase
Dim doc As notesdocument
Set curdatabase = uiworkspace.currentdatabase
Set database = curdatabase.database
Set doc = database.getdocumentbyid("00000C62")
Call uiworkspace.EditDocument(True,doc,False )
End Sub
我如何实现归档,比如我如何把当前视图中所有被选中的文档归入文件夹 fold 中?
答: 用Script象如下这样实现:
Sub AddDocToFold(fold As String)
Dim uiworkspace As New notesuiworkspace
Dim uiview As notesuiview
Dim doc As NotesDocument
Dim docList As String
Set uiview = uiworkspace.currentview
For j = 1 To uiview.Documents.Count
Set doc = uiview.Documents.GetNthDocument(j)
Call doc.PutInFolder( fold )
Next
End Sub
我如何实现把某文件夹视图中的被选择的文档从该文件夹中清除,但却不能删除他们?
答: 用Script 实现如下:
Sub RemoveDocFromFold( fold As String,all As Integer)
'功能:
' 把文档从某个文件夹中移走,但并不删除此文档
'参数:
' fold: 文件夹
' all : 0表示仅移走当前选择的文档,1表示移走该文件夹中所有文档
Dim uiworkspace As New notesuiworkspace
Dim uiview As notesuiview
Dim doc As NotesDocument
Dim view As notesview
Set uiview = uiworkspace.currentview
Set view = uiview.view
If all = 0 Then '移去所选文档
For j = 1 To uiview.Documents.Count
Set doc = uiview.Documents.GetNthDocument(j)
Call doc.RemoveFromFolder( fold )
Next
Else
If all=1 Then '移去全部文档
Set doc = view.GetFirstDocument
'遍列该视图的所有文档,获取所有满足条件的纪录数
While Not(doc Is Nothing)
Call doc.RemoveFromFolder( fold )
Set doc = view.GetNextDocument(doc)
Wend
End If
End If
'Evaluate("@Command([ViewRefreshFields])")
End Sub
我如何把当前视图中的所有的被选择的文档的某个域的值替换掉?
答: 用Script 实现如下:
Sub SelectedDocFieldReplace( Field As String,repval As String)
'功能:
' 把所选文档中的每个 Field 域的值 改为 repval
'参数:
' Field 要更改的域的名称
' repval 修改后的域值
Dim uiworkspace As New notesuiworkspace
Dim uiview As notesuiview
Dim doc As NotesDocument
Dim order_num As String
'order_num = Inputbox$("请输入批次")
Set uiview = uiworkspace.currentview
For j = 1 To uiview.Documents.Count
Set doc = uiview.Documents.GetNthDocument(j)
On Error Goto lable1
Call doc.replaceitemvalue(Field,repval)
Call doc.save(True,False)
Next
Exit Sub
lable1:
Msgbox("错误!,所选文档没有指定的域,这个错误发生在没有给 selectedDocFieldReplace() 函数传递正确的参数")
Exit Sub
End Sub
我如何创建某个程序运行结果的日志文档?
答: 首先新建一个日志文档的表单,并把该表单设置成数据库的默认表单,然后 就用Script创建文档,并填写该文档中某些域的值,最后存盘,例子程序片段如下:
'写传真日志
Dim faxerdoc as notesdocument
‘faxerr_receiver,faxerr_docnum,faxerr_content是表单form_faxerr的三个域名

Set faxerrdoc = New NotesDocument( db )
faxerrdoc.Form = "form_faxerr"
Call faxerrdoc.replaceitemvalue("faxerr_receiver",Cstr(peoplecount) )
Call faxerrdoc.replaceitemvalue("faxerr_docnum",strsucssnding )
Call faxerrdoc.replaceitemvalue("faxerr_content",faxerrmsg )
success = faxerrdoc.ComputeWithForm( False, False )
If success Then
Call faxerrdoc.Save( True, False )
Else
Msgbox("无法写入传真日志....")
End If
'Msgbox(faxerrmsg)
Exit Sub
我要从当前视图中选择一批文档,并让程序从这些文档中提取信息,在嵌入在表单中的OLE对象Word文档中建立一张表,要求是选择了几篇文档就在这张表中画几行,这张表的每个列的信息都中文档中的域中提取,换句话说,就是要把被选文档以Word文档表格的形式表示出来,能否给我一个这方面的例子程序?
答: 可以,下面就是这样的一个例子:
Sub inputgroupplan(source As notesuidocument,doccollection As notesdocumentcollection)
'功能: 自动生成出团计划表。
' 详细描述:
' 从 文档集合 doccollection 中提取各个域值,并把提取的信息以一定
' 的表格形式送入当前文档的 body 域中的 OLE 对象--Word 文档中.
'参数:
' source: 当前文档
' doccollection :文档集(比如文档的选择集)

Dim session As New NotesSession '当前会话
Dim counter As Integer '计数器
Dim doccustom As NotesDocument 'notes 文档对象
Dim thisdoc As Variant 'Word 文档对象
Dim thisrange As Variant 'Word 开发中的 range 对象
Dim thispicture As Variant '嵌入Word 文档的图象对象
Dim thistable As Variant '嵌入Word 文档的表格对象
Dim pagehead As String '嵌入Word 标题
'获取嵌入文档的句丙
If source.EditMode Then
Set thisdoc = source.getobject("oleobject")
'插入一幅图
Set thispicture = thisdoc.shapes.Addpicture("c:/学习/cassiatb.jpg")
'设置图像属性
With thispicture.wrapformat '环绕方式
.type = wdwrappicture '类型为picture
.side = wdwrapright '文字右环绕
End With
'设置该文档的页面设置的左边距为20个单位(象素)
With thisdoc.pagesetup
.leftmargin = 20
.rightmargin = 20
End With
counter=0
pagehead = Inputbox$("请输入标题")
pagehead = Chr(10) & pagehead & Chr(10) & Chr(10) & Chr(10)
'Call source.FieldSetText ( "Namelist_Group_Num", group_num )
'groupstring = "Namelist" & " " & group_num & Chr(10)
Set thisrange = thisdoc.range(1,1)
thisrange.InsertBefore (pagehead)
Set thisrange = thisdoc.range(2,Len(pagehead))
With thisrange
.bold = True '加粗
.ParagraphFormat.Alignment = 1'wdAlignParagraphCenter 行居中
.font.size = 20 '字体大小为20
End With
Set doccustom = doccollection.GetFirstDocument
'遍列文档集的所有文档,获取所有满足条件的纪录数
While Not(doccustom Is Nothing)
counter=counter+1
Set doccustom = doccollection.GetNextDocument(doccustom)
Wend
'动态分配纪录数组
'Redim record(counter,6) As String
'插入一张表
Set thisrange = thisdoc.range(Len(pagehead)+1,Len(pagehead)+1)
Set thistable = thisdoc.tables.Add(thisrange, counter+1, 8)
'thistable.autoformat(False)
'写表头
thistable.rows(1).cells(1).range.insertbefore("前往国家")
thistable.rows(1).cells(2).range.insertbefore("国家数")
thistable.rows(1).cells(3).range.insertbefore("天数")
thistable.rows(1).cells(4).range.insertbefore("出境城市")
thistable.rows(1).cells(5).range.insertbefore("入境城市")
thistable.rows(1).cells(6).range.insertbefore("出发日期")
thistable.rows(1).cells(7).range.insertbefore("同行价")
thistable.rows(1).cells(8).range.insertbefore("市场指导价")
'恢复计数器
counter = 0
'写表内容
Set doccustom = doccollection.GetFirstDocument
While Not(doccustom Is Nothing)
counter = counter+1
thistable.rows(counter+1).cells(1).range.insertbefore(doccustom.plan_country(0))
thistable.rows(counter+1).cells(2).range.insertbefore(doccustom.plan_country_num(0))
thistable.rows(counter+1).cells(3).range.insertbefore(doccustom.plan_day(0))
thistable.rows(counter+1).cells(4).range.insertbefore(doccustom.plan_out_city(0))
thistable.rows(counter+1).cells(5).range.insertbefore(doccustom.plan_in_city(0))
thistable.rows(counter+1).cells(6).range.insertbefore(doccustom.plan_date(0))
thistable.rows(counter+1).cells(7).range.insertbefore(doccustom.plan_whole_price(0))
thistable.rows(counter+1).cells(8).range.insertbefore(doccustom.plan_mart_price(0))
Set doccustom = doccollection.GetNextDocument(doccustom)
Wend
End If
End Sub
如何实现表单上的内容根据用户的输入动态变化?
答: 一般可以用notes的隐藏属性功能来控制,使用当公式为真是隐藏,然后靠公式来控制具体怎样隐藏.比如可以在对话筐上放一个对话筐列表,里面放十个选项,当用户选择了其中的某几个选项时,响应的在下面的表单部分显示几行.这可以画一个表格,这个表格的属性中设置边框的线条粗细为零.然后对应十个选项分为十行,每行填入和选项响应的内容,然后选定某一行的所有文本,编辑其隐藏属性,选当公式为真时隐藏,这个公式您就可以写成当选项的被选中条目中不包含本行文字时隐藏就可以了,这样这一行就会在响应的选项被选中时才会显示.
notes没有应用程序级的公共变量,那么我如果要弹出一个对话筐,并从这个对话筐中返回很多用户输入,我该怎么办?
答: 你首先要确定你要在哪个表单上弹出这个对话筐,一般的的做法是利用notes 的域值传递功能,即如果对话筐中的表单上的域和弹出这个对话筐的母表单的域相同,则对话筐中的值会被自动传到母表单的相应的域中的.你可以将放在对话筐中的表单作成子表单,同时把它放入对话筐中和母表单中,这样你就可以接受到所有的用户输入了.
                                              在b/s下从通讯录中选人一例在parentform母表单上建一个域reader(多值),另建一表单(childform),上建两个对话框域rylb(通讯录中人员的姓名),reader(用于添加或删除人员),在母表单上建一按钮,js代码如下:window.open('/xxxy/xxx.nsf/childform?openform',null,'width=450,height=200,status=no,resizable=no,top=200,left=200,scrollbars=no');在childform表单的js head 中写入以下代码,var fieldfunction AddClick(field){var fieldEntryList=document.all["rylb"]if(fieldEntryList.selectedIndex != -1){for (var i=0; i < fieldEntryList.options.length; ++i){if (fieldEntryList.options[i].selected)field.options[field.options.length] = new Option(fieldEntryList.options[i].text);}}}function RemoveClick(field){if (field.length != 0){if (field.selectedIndex != -1){for(var i=0;i<field.options.length ; ++i){if (field.options[i].selected){field.options[i] = null;--i;}}}}}在childform表单的onload中写入以下代码,(用于读取原有的值)if(window.opener){readers=window.opener.document.forms[0].reader.value;var arr=readers.split(";");var field=document.all["reader"]for(i=0;i<arr.length;i++){field.options[field.options.length] = new Option(arr[i]);}}在childform表单中的rylb域中的“为选项使用公式”中写入公式@Unique( @DbColumn("";"":"names.NSF";"people";1)),在HTML样式中写入“width=130;height=150”在childform表单中的reader域中HTML样式中写入“width=130;height=150”;在childform表单上建四个按钮,“添加->”按钮,代码如下:AddClick(reader),“移除<-”按钮,代码如下:RemoveClick(reader),“确定”按钮,代码如下:var fieldEntryList=document.all["reader"]if (fieldEntryList.options.length=="0"){window.opener.document.forms[0].reader.value=""}else{window.opener.document.forms[0].reader.value="";for (var i=0; i < fieldEntryList.options.length; ++i){if(window.opener.document.forms[0].reader.value==""){window.opener.document.forms[0].reader.value=fieldEntryList.options[i].text}else{window.opener.document.forms[0].reader.value=window.opener.document.forms[0].reader.value+";"+fieldEntryList.options[i].text}}}alert("操作成功,请保存原文档!")window.close()“取消”按钮,代码如下:window.close()这样就可以对parentform母表单域中的值进行添加、移除等自如地操作。


 
Servlet在Cookie,Session和上传文件上的应用
 
Servlet可以被认为是服务端的applet,它被WEB服务器加载和执行,前端可以显示页面和获得页面数据,后台可以操纵数据库,能完成JavaBean的很多功能。在这里我较为详细的说说Servlet在Cookie,Session和上传文件上的应用,在说明时我给出一些能编绎运行的小例子,最后给出一个文件上传例子以加深印象。
  我们先来看看SERVLET程序的基本构架:
式1:
  package test;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.io.*;
  import java.util.*;
  public class test extends HttpServlet {
  public void init(ServletConfig config) throws ServletException {
  super.init(config);
}
  public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  int f =1; switch(f){
  case 1:firstMothed(request,respponse);break;
}
}
  public void firstMothed(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  response.setContentType("text/html");
  OutputStreamWriter osw = new
  OutputStreamWriter(response.getOutputStream());
  PrintWriter out = new PrintWriter (response.getOutputStream());
  out.println("< html>");
  out.println("< head>< title>Servlet1< /title>< /head>");
  out.println("< body>你好!");
  out.println("< /body>< /html>");
  out.close();
}
}
式2:
  package test;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.io.*;
  import java.util.*;
  public class test extends HttpServlet {
  public void init(ServletConfig config) throws ServletException {
  super.init(config);
}
  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  response.setContentType("text/html");
  OutputStreamWriter osw = new OutputStreamWriter(response.getOutputStream());
  PrintWriter out = new PrintWriter (response.getOutputStream());
  out.println("< html>");
  out.println("< head>< title>Servlet1< /title>< /head>");
  out.println("< body>你好!");
  out.println("< /body>< /html>");
  out.close();
}
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  response.setContentType("text/html");
  OutputStreamWriter osw = new OutputStreamWriter(response.getOutputStream());
  PrintWriter out = new PrintWriter (response.getOutputStream());
  out.println("< html>");
  out.println("< head>< title>Servlet1< /title>< /head>");
  out.println("< body>你好!");
  out.println("< /body>< /html>");
  out.close();
}
}
  式1适合于作总控模块,此SERVLET作中间调度,根据不同的f值调用不同的SERVLET或方法。
式2适合于对html的get和post有不同要求的情况。
  但这并不是绝对的,式2就完全可以代替式1,只要在doGet()方法中写上doPost就与式1完全一样。
在init方法中执行的语句,只要这个servlet被启动了就一直有效,比如,我们在init()中new了一个对象,那么这个对象的内存空间就永远存在,除非显式地把这个对象赋为null,或重启服务。
  HttpServletRequest和HttpServletResponse两个对象实现http请求,它们有很多有用的方法,在下面的cookie和session管理中会细加描述。
  1, cookie管理 cookie用于在客户端保存个人所特有的信息,它采取在客户机写临时文件的机制。
  package test;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.io.*;
  import java.util.*;
  public class test extends HttpServlet {
  public void init(ServletConfig config) throws ServletException {
  super.init(config);
}
  public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  //写cookie
  String CookieName ="js79"; //若是汉字则需编码
  String CookieValue = "yesky";//若是汉字则需编码
  Cookie cookie = new Cookie(CookieName,CookieValue);
  cookie.setMaxAge(age); // age = Integer.MAX_VALUE 永不过期
  cookie.setPath("/");
  //读cookie
  String value = null;
  Cookie[] cookies = request.getCookies();
  if (cookies != null) {
  for (int i=0; i< cookies.length; i++) {
  if (cookies[i].getName().equals(CookieName))
  value = cookies[i].getValue();
  break;
}
}
}
  response.setContentType("text/html");
  OutputStreamWriter osw = new OutputStreamWriter(response.getOutputStream());
  PrintWriter out = new PrintWriter (response.getOutputStream());
  out.println("< html>");
  out.println("< head>< title>test< /title>< /head>");
  out.println("cookie键:"+CookieName+"< br>");
  out.println("cookie值: "+value);
  out.println("< /body>< /html>");
  out.close();
}
}
  2,session管理
  Session在Servlet中是很有用的,它比cookie安全可靠灵活,但是管理起来有点麻烦,用得不好会造成服务器的开销很大,浪费资源。下面是一个基于Session管理一个对象的简单例子。
一个简单的bean对象TestObject
  package test;
  public class TestObject extends Object {
  int id = 0; public String cur="";
}
  package test;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.io.*;
  import java.util.*;
  public class TestMan extends HttpServlet {
  public void init(ServletConfig config) throws ServletException {
  super.init(config);
}
  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  int f = 1;
  if(request.getParameter("f")!=null)
  f =
  Integer.parseInt(request.getParameter("f"));
  switch(f){
  case 1: this.getResult(request,response);
  break;
  case 2:
  this.setSession(request,response);
  break;
}
}
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  doGet(request,response);
}
  public void getResult(HttpServletRequest request, HttpServletResponse response)   throws ServletException, IOException {
  TestObject testObject = null;
  testObject = getStatus(request,response);
  String html = testObject.id; doWrite( response,html);
}
  public void setSession(HttpServletRequest request, HttpServletResponse response)  throws ServletException, IOException {
  HttpSession session = request.getSession();
  TestObject testObject = null;
  testObject = getStatus(request,response);
  String tmp = null;
  tmp = request.getParameter("id");
  if(tmp != null) testObject.id = tmp;
  session.putValue("testObject ",article);
  getResult(request,response);
}
  private TestObject getStatus(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  HttpSession session = request.getSession();
  TestObject testObject = null;
  if(session!=null){
  if(session.getValue("testObject ")!=null){
  testObject = (TestObject)session.getValue("testObject ");
}
  else{
  testObject = new TestObject ();
}
}
  else{
  testObject = new TestObject ();
}
  return testObject;
}
  private void doWrite(HttpServletResponse response,String html) throws   ServletException, IOException {
  PrintWriter out = response.getWriter();
  out.println(html);
  out.close();
}
  /////////////////////////////////////////////////////////
}
  若能轻松搞定上面的例子,相信读者对SERVLET已有了较为深刻的理解。
下面再介绍一个上传文件例子,其中汲及到了下载的免费JavaBean (如有感兴趣的朋友,可来函索要免费JavaBean源代码,Email:js79@yesky.com)
  上传基本原理:由页面发出一个http请求,服务端得到请求后,解析多媒体协议,读出文件内容,写文件内容到服务器,所有的这些功能都封装到JavaBean中。
  上传文件的必需条件:Browser端< form>表单的ENCTYPE属性值必须为 multipart/form-data,它告诉我们传输的数据要用到多媒体传输协议,由于多媒体传输的都是大量的数据,所以规定上传文件必须是post方法,< input>的type属性必须是file。
  package upload;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.io.*;
  import java.util.*;
  public class UpLoadServlet extends HttpServlet {
  public void init(ServletConfig config) throws ServletException{
  super.init(config);
}
  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  PrintWriter out = response.getWriter();
  out.println("< HTML>< HEAD>< TITLE>UpLoad< /TITLE>"
  +"< meta http-equiv='Content-Type' content='text/html; charset=gb2312'>"
  +"< /HEAD>"
  +"< body>");
  out.println("< div align='center' valign='top'>"
  +"< span class='nava'>请你选择上传的文件(请注意文件大小只能在20K之内)< /span>< BR>"
  +"< form ENCTYPE='multipart/form-data' method=post action=''>"
  +"< input type='file' name='file'>"
  +"< input type='submit' value='发送'>"
  +"< /form>"
  +"< /div>");
  out.println("< /body>< /html>");
  out.close();
}
  ////////
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  PrintWriter out = response.getWriter();
  int tmpID = 1; try {
  MultipartRequest multi = new MultipartRequest(request,"/home/js79/html/", 5 * 1024 * 1024);
}
  catch(Exception e){
  tmpID = -1; System.out.println(e);
}
  if(tmpID == 1){
  out.println("< HTML>< HEAD>< TITLE>UpLoad< /TITLE>"
  +"< meta http-equiv='Content-Type' content='text/html; charset=gb2312'>"
  +"< /HEAD>"
  +"< body>");
  out.println("上传成功!< /body>< /html>");
}
  else{
  out.println("< HTML>< HEAD>< TITLE>UpLoad< /TITLE>"
  +"< meta http-equiv='Content-Type' content='text/html; charset=gb2312'>"
  +"< /HEAD>"
  +"< body>");
  out.println("上传不成功!< /body>< /html>");
}
  out.close();
}
}

讨论notes在b/s模式下如何才能达到最佳效果!
 
 
1、要充分利用CSS样式表,将你的域和文本尽可能放到表格单元格中,在样式中可加入CSS,还有在HTML 首页内容中加入Css
2、在B/S模式下,做到页面的切换,要充分利用JavaScript;
3、在B/S下,要在表单中加入相应的html,也不是单纯的加入,而且要结合notes的公式语言;
4、需要注意的,所有的Domino设计元素以及文档都有自己的特定的ID号,当需要生成某些URL时,Domino就使用这些特定的ID号来代表该元素;
5、在B/S下,如果你的不同视图具有一致的风格,那就需要采用$$ViewTemplate for “视图的名字”的方式;
6、还有就是需要CGI变量。
 
在notes里头好多东西在http下实现不了,不知各位怎样解决的?
                                     功程序员的七个秘密
作者 Merrion

7项使你成为开发者社区出色成员的技巧
 
1.为人的需求编码
计算机界的一个最普遍的误会是认为原始码是为计算机服务。计算机是工作在低级的二进制代码上,是在一系列的难以理解的1或0或者十六进制数字之上,而不是我们敲入的结构化高级计算机语言。
这些语言被开发出来的目的是为了帮助我们程序员。
在实践中,为人的需求编码通常意味着首先要有清楚透明的结构和思路,其次才是效率和速度。
2.经常的好的注释
注释是为人的需求编码的一种极端的语言元素例子。大多数编译器都会将注释与可执行程序分离开来。
注释的目的就是要告诉你(或者其他将来的开发着)程序的功能是什么。
把这些写进注释 - 并且避免只是简单重新叙述代码。
好的注释:
Disable button to prevent its activation
糟糕的注释:
Set cmd = False
一个很好的检验注释的标准是:如果只有注解没有去掉,有人能够知道你的程序的作用吗?
3.良好代码布局增加易读性
正如一位作家将一本书分成章和段落以帮助阅读,因此开发者考虑代码如何布局如何能能增强代码的易读性也是非常重要的。
特别在任何语句结构块中 (如
IF.
.
THEN.
.
.
ELSE语句快) 和任何循环结构(如
WHILE.
.
.
END WHILE)
代码应该缩进以便于很容易分清楚哪里是开始哪里是结束。
4.预测并处理 那意想不到的事情
你打开一个文件之前,确定文件为当前文件。你将焦点设定为控件之前,确定控制是看得见的并且可用。设法找出在什么条件下使你的代码失效,并在你的程序崩溃之前测试它。
5.命名变量增强易读性
有许多策略进行变量命名。首要原则是一致性和尽可能的能通过命名变量提供足够的信息。如果你命名一个变量为nMonth,你提供给程序员的信息是这个变量将被设计用来存储什么内容。
我自己倾向于使用匈牙利的命名风格 - 但是无论你使用哪一种风格,一致性是最主要的。
6.使你的方法和过程保持简洁
一个方法或者过程理想状况下应该只做一件事情。在我的经验方面,最大的误区是,一个过程通常被设计来完成许多的不同操作。
应该将他们按不同的操作拆分成各个不同方法处理各自的事情,这样各个功能可以很容易被重用,而且各个方法内的代码改变也很容易理解。
7.适当的方法和变量使用范围
功能和变量如果只在一个组件中使用,则在那一个组件之外不应该是看得见的。如果变量只被设计用于一个方法或者过程,那么在那一个方法或过程之外不应该是看得见的。
这可以避免任何一个变量或者方法在它没有意义的地方使用。

有许多其他的提示和技巧能使你被成为比较好的程序员, 将会使你更有效率而你写的程序也更可维护, 但是这上面列出的七个秘密将会视为一个好的基础 - 然而使你高度地建构在他们之上。
 
公式秘籍
 
访问用户环境

用户环境是包含数据库的服务器或工作站,数据库包括以下内容: 复制公式、由新邮件到达时或定时触发的代理、选择公式或列公式。否则,用户环境是用户运行公式的 Notes 工作站。
用户名可以是专有名称也可以是非专有名称,专有名称可以是规范或缩写的,使用 @Name 可更改用户名的格式。
以下函数返回或处理用户环境的信息。
函数 描述
@UserName 返回用户名或服务器名。
@Name([key]; name) 更改用户名的格式。关键字包含 [CN] 以从一个专有名字中解析出公共名,[Abbreviate] 缩写规范格式的专有名字,[Canonicalize] 与上述作用相反,[ToKeyword] 将名字各部分按相反顺序排序,用反斜杠分开(用于分类视图)。
@UserRoles 对于服务器上的数据库,返回当前用户的角色列表。
@MailDbName 返回用户邮件数据库的服务器名和路径名。该 @function 计算出一个包含两元素的列表。
@OptimizeMailAddress(address) 从地址中删除无用的网络域。
@Platform 返回用户当前运行的平台:Macintosh、 NetWare、 OS2V1、 OS2V2、 UNIX、 Windows/16 或 Windows/32。
@Version 返回正在运行的 Notes 版本(字符串)。
@Password(string) 对字符串加密。加密后别人无法从中得到最初的字符串。


访问当前数据库和视图

可以直接访问正在运行公式的数据库(便捷图标除外,因为它没有数据库环境)。也可以在视图环境中直接访问正在运行公式的视图。同样,在文档环境中可直接访问打开文档的视图。
数据库和视图属性
下表列出返回数据库和视图属性的函数。
函数 描述
@DbManager 返回当前对数据库有“管理者”权限的用户、群组和服务器。返回一个列表。
@DbName 返回当前 Notes 服务器和数据库的名称。返回二个元素的列表。
@ViewTitle 返回当前视图的标题。
窗口标题和列公式 @function
许多 @function 提供了关于视图的答复层次和其他方面的信息。在视图中,主文档以 1、2、3 等编号。每组答复文档或答复的答复文档则有第二个和第三个层次的从 1 开始的编号。缺省情况下,完整的答复文档的编号以小数形式出现。例如:第三个主文档的第二个答复文档编号为 3.2,而它下面的第一个答复文档编号为 3.2.1。
这些函数仅工作于窗口标题和列公式中,有一些会被限制。返回值都是一个字符串。
函数 描述
@Responses 返回当前视图中当前文档的答复文档的编号(只限于窗口标题公式)。
@DocLevel 返回在当前视图中当前文档的级别。
@DocSiblings 返回与当前文档同级的文档编号(包含当前文档)。
@DocNumber 返回当前视图中的当前文档或分类的编号。
@DocNumber(sep) 同上,只是用 sep,而不是句点来分隔编号。
@DocNumber("") 同上,只是仅返回编号的最右边部分。
@DocParentNumber 返回当前视图中当前文档或分类的父文档或父分类的编号。
@DocParentNumber(sep) 同上,只是用 sep,而不是句点分隔编号。
@DocParentNumber("") 同上,只是仅返回编号的最右部分。
@DocDescendants 返回后续文档的编号。包含当前文档的子文档,子文档的子文档。
@DocDescendants(def) 同上,只是返回 def。在 def 中使用 % 以表示编号。
@DocDescendants(zero; def) 同上,只是如果没有后续文档的话则返回 zero。
@DocDescendants(one, zero; def) 同上,如果只有一个后续文档的话,则返回 one。
@DocChildren 返回当前文档的直接子文档的编号。
@DocChildren(def) 同上,仅返回 def。在 def 中使用 % 以表示编号。
@DocChildren(zero; def) 同上,只是如果没有后续文档的话,则返回 zero。
@DocChildren(one, zero; def) 同上,如果只有一个后续文档的话,则返回 one。
@IsCategory 如果当前行的当前域右边任何域是一个分类,则返回一个星号。
@IsCategory(True) 同上,只是返回 Frue 代替星号。
@IsCategory(True; False) 同上,但是如果没有分类域,则返回 False。
@IsExpandable 如果当前行是可展开的,则返回一个加号。
@IsExpandable(True) 同上,只是返回 True 代替加号。
@IsExpandable(True; False) 同上,但是如果当前行是不可展开的,则返回 False。

使用 @function 通过 LS:DO 访问外部数据库

以下 @function 通过 ODBC 访问外部数据库并返回一个值或值的列表:
@DbColumn 返回表的一列中的全部值,或者全部的不同的值。
@DbLookup 返回表的一列中通过关键字匹配选定的值。
@DbCommand 将一个命令传递给外部的数据库管理系统(DBMS)并返回结果。
@DbColumn 与 @DbLookup 都只能提取数据。它们不能增加、删除、修改数据或执行其他操作。@DbCommand 能提取数据或发送其他可以更改数据的 SQL 语句。LotusScript 提供了包括更新外部数据库的更加强大的功能。
前四个参数对于三个函数是同样的,通过 ODBC 建立访问数据库。这些参数是:
"ODBC" 是字符常量;或 "ODBC" : "NoCache"
定义在数据源表格中的数据源名称(在 Windows 中的 ODBC.INI)
用户标识符,两个用户标识符列表,或者一个空串,根据外部数据源而定
口令,两个口令列表,或者一个空串,根据外部数据源而定
(@DbColumn 和 @DbLookup) 要访问的表的名称
(@DbCommand) 要执行的命令
(@DbColumn 和 @DbLookup) 要访问的列的名称
处理由数据源返回的空数据的选项
(@DbLookup) 包含关键字的列名
(@DbLookup) 适当的数据类型的关键字值,或者是一个列表
(@DbColumn 和 @DbLookup) 两个元素的列表:“Distinct”作为关键字或空串;“Ascending”或“Descending”作为一个关键字
在需要用 IDS 和口令的地方,您可以指定空串并让用户在执行函数时提供它们。

使用 @function 通过 LS:DO 访问外部数据库

1. 该公式取得 MANUAL 表中的 PARTNO 列。
@DbColumn("ODBC";"Oracle";"";"";"MANUALS";"PARTNO";"":"Ascending")
2. 该公式从 MANUALS 表的行中取得 TITLE,在该行中 PARTNO 是 17-895A。
@DbLookup("ODBC";"Oracle";"";"";"MANUALS";"TITLE";"PARTNO";"17-895A")
3. 该公式从 MANUALS 表中的 ONHAND 列的数字值小于 100 的每行中取得 PARTNO 列值。
@DbCommand("ODBC";"Oracle";"";"";"SELECT PARTNO FROM MANUALS WHERE ONHAND <100")

转换数据类型

在对数据操作时类型必须是正确的。以下的函数用来转换数据和测试数据类型。
函数 描述
@Text(value) 将一个值转换为文本字符串。
@Text(value; format) 根据指定的格式将一个数字或时间-日期值转换成文本字符串。
@TextToNumber(string) 将文本字符串转换成一个数字。
@TextToTime(string) 将文本字符串转换成一个日期-时间值。
@IsText(value) 如果值是文本字符串或文本字符串列表,则返回“真”值 (1)。
@IsNumber(value) 如果值是数字或数字列表,则返回“真”值 (1)。
@IsTime(value) 如果一个值是时间-日期或时间-日期列表,则返回“真”值 (1)。
@Char(number) 将一个 IBM 代码页 850 代码转换为对应的字符。


连接、比较和判定长度


运算符 + 用来连接字符串。运算符 =、<>、!=、=!、><、<、>、<= 和 >= 用来比较字符串。以下的函数用来判定字符串的长度和比较字符串:
函数 描述和用法
@Length (string) 以字符为单位返回字符串的长度。
@Length(stringlist) 以字符为单位,返回字符串列表中每个元素的长度。
@Matches (string; pattern) 判定两个字符串是否匹配。可以用通配符来扩展比较的范围。
@Like (string; pattern) 判定两个字符串是否匹配。遵循 ANSI SQL 标准。
@Like(string; pattern; esc) 与上面的一样只是多了一个转义字符。
@Matches @Matches 用“?”来匹配任意一个单独的字符,用“*”来匹配任意的字符序列,@Matches 使用“/”作为转义字符。
@Like 按照 ANSI X3.135-1992 标准,使用“_”(下划线)来匹配任意一个单独的字符,用“%”(百分号)来匹配任意的字符序列。

查找并提取子串

样例
以下函数查找并提取子串:
函数 描述
@Contains(string; sub) 判定一个字符串是否包含一个子串。
@Contains(string; list) 判定一个字符串是否包含一个列表中的子串。
@Begins(string; sub) 判定一个字符串是否以一个子串开始。
@Ends(string; sub) 判定一个字符串是否以一个子串结束。
@Left(string; n) 从一个字符串中提取最左边的 n 个字符。
@Left(string; sub) 从一个字符串中提取最左边的字符,直到一个子串为止,从左到右搜索。
@LeftBack (string; n) 从一个字符串中提取最左边的字符,直到从右边开始的第 n 个字符为止。
@LeftBack(string; sub) 从一个字符串中提取最左边的字符,直到一个子串为止,从右到左搜索。
@Right(string; n) 从一个字符串中提取最右边的 n 个字符。
@Right(string; sub) 从一个字符串中提取最右边的字符,直到一个子串为止,从左到右搜索。
@RightBack (string; n) 从一个字符串中提取最右边的字符,直到从左边开始的第 n 个字符为止。
@RightBack(string; sub) 从一个字符串中提取最右边的字符,直到一个子串为止,从左到右搜索。
@Middle(string; off; n) 从一个字符串中提取 n 个字符,从一个偏移量开始,从左到右搜索。
@Middle(string; sub; n) 从一个字符串中提取 n 个字符,从一个子串开始,从左到右搜索。
@Middle(string; off; sub) 从一个字符串中提取字符,从一个偏移量开始,到一个子串结束,从左到右搜索。
@Middle(string; sub; sub) 从一个字符串中提取字符,从一个子串开始,到另一个子串结束,从左到右搜索。
@MiddleBack(str; off; n) 从一个字符串中提取 n 个字符,从一个偏移量开始,从右到左搜索。
@MiddleBack(str; sub; n) 从一个字符串中提取 n 个字符,从一个子串开始,从右到左搜索。
@MiddleBack(str; off; sub) 从一个字符串中提取字符,从一个偏移量开始,到一个子串结束,从右到左搜索。
@MiddleBack(str; sub; sub) 从一个字符串中提取字符,从一个子串开始,到另一个子串结束,从右到左搜索。
@ReplaceSubstring(source; from; to) 在 source 中用 from 的内容替换 to 的内容。如果 from 和 to 都是列表,按次序替换对应的项目。
@Word(string; sep; n) 从 string 提取单词 n,其中单词是在指定的分隔符之间的文本。
@Word(list; sep; n) 从列表中的每个字符串中提取单词 n,其中单词是在指定的分隔符之间的文本。


修剪、重复、添加新行,并改变大小写

样例

以下函数修剪字符串、重复字符、添加新行(回车),并改变大小写:
函数 描述
@Trim(string) 从字符串中删除开头、结尾和多余的空格。
@Trim(list) 从字符串列表的每个元素中删除开头、结尾和多余的空格,并从列表中删除空白元素。
@Repeat(string , number) 将字符串重复若干次。
@NewLine 在文本字符串中插入一个新行(回车)。
@LowerCase(string) 将字符串中所有的大写字符转换成小写。
@UpperCase(string) 将字符串中所有的小写字符转换成大写。
@ProperCase 将字符串中每个单词的第一个字符转换成大写,并将其余的字符转换为小写。


执行算术运算

乘、除、加、减运算符(*/+-)。乘法和除法运算符的优先级高于加法和减法;运算顺序是从左到右。但可以使用括号更改运算顺序。以下列出的是算术运算函数。
函数 描述
@Abs(number) 计算一个数的绝对(无符号)值。
@Sign (number) 对于正数返回 1,对于负数返回 -1,对于零则返回 0。
@Sum(num; num; ...) 计算数字和数字列表的和。
@Integer(number) 去掉数字的小数部分,使它成为整数。
@Integer(numlist) 去掉数字列表所有元素的小数部分,使它们成为整数。
@Round(number) 对一个数字进行四舍五入。
@Round(number; factor) 以一个指定的因子规整数字。
@Round(numlist) 对数字列表中的每个数字进行四舍五入。
@Round(numlist; factor) 以一个指定的因子规整数字列表中的每个元素。
@Max(number; number) 取两个数中较大的一个。
@Max(numlist; numlist) 对两个数字列表进行矩阵式操作时,取两个数字中较大的一个
@Min(number; number) 取两个数中较小的一个。
@Min(numlist; numlist) 对两个数字列表进行矩阵式操作时,取两个数字中较小的一个。
@Modulo(number; number) 计算一个数字被第二个数字除后得出的余数。
@Modulo(numlist; numlist) 对两个数字列表进行矩阵式操作时,计算一个数字被第二个数字除后得出的余数。
@Power(base; exp) 指数计算。
@Sqrt (number) 计算平方根。
@Pi 取得圆周率。
@Log(number) 计算常用(以 10 为底)对数。
@Ln(number) 计算自然(以 e 为底)对数。
@Exp(number) 计算以 e 为底的指数。
@Random 返回一个 0 到 1 之间的随机数。
@Sin(angle) 计算角度的正弦(弧度)。
@Cos(angle) 计算角度的余弦(弧度)。
@Tan(angle) 计算角度的正切(弧度)。
@Asin(sine) 反正弦函数。
@Acos(cosine) 反余弦函数。
@Atan(tangent) 反正切函数。
@Atan2(x; y) 计算以 y/x 作为正切值的反正切函数。

答复列的公式

仅用于答复的列需要公式来生成总结答复文档的文本。
包含作者的信息
讨论数据库可以使用如下答复列的公式来显示答复文档的作者、日期和主题:
From + " added this comment: " + Subject + " (" + @Text(@Created) + ")"
按以下方式显示答复:
Stephanie Mahar added this comment: Great job! (10/10/97 04:43:15 PM)
跟踪文档状态
在雇员信息数据库中,答复列可以显示新雇员以及离职雇员的调查信息,作为“按雇员姓名”视图中常规的“雇员记录”的答复文档。下面公式根据答复文档使用的表单的不同,显示不同的消息,并且显示文档的邮递状态。
@If(Form = "Exit"; "Exit Form, "; "New Hire Information, ") + @If(Mailed = "Yes"; "mailed to employee " + @Text(@Date(PostedDate)); "not yet mailed")
如果答复文档使用 Exit 表单,那么答复行显示也许如下:
Exit Form, mailed to employee 08/26/97
如果答复文档使用 New Hire Information 表单,那么答复行显示也许如下:
New Hire Information, not yet mailed
跟踪答复的数目
可以使用 @DocDescendants 跟踪答复文档的数目,以便作者迅速知道收到多少答复文档。下面的主文档列(不是答复列)公式对于答复样式的视图非常有用。
Subject + " (" + @Name([CN]; From) + @DocDescendants(")"; ", % response)"; ", % responses)")
如果是主文档,那么列显示 Subject 域的内容、作者名称、答复以及答复的答复文档的数目。如果文档有一个答复,那么列显示“response”;否则显示“responses”。主文档行可作如下显示:
Need Help with Trade Show (Indy Montoya, 1 response)
Changing the Product Name (Sandy Braun, 2 responses)

 

视图小程序编程
通过使用一些 @commands 命令可以对视图小程序编程。有以下命令:
@command 描述
ViewCollapse 折叠所选文档
ViewExpand 展开所选文档
ViewCollapseAll 折叠所有文档
ViewExpandAll 展开所有文档
ViewRefreshFields 刷新视图。
刷新时,视图小程序不删除标记为已删除的文档
MoveToTrash 使当前所选文档标记为已删除
EmptyTrash 永久删除标记为已删除的文档
Folder 将所选文档移动或拷贝到文件夹
RemoveFromFolder 从当前文件夹删除所选文档。


用新建表单增强Notes打印功能中央财经大学 邹龙泉
02-1-31 下午 01:56:09

Lotus公司推出的Lotus Domino/Notes作为办公自动化系统的平台近年来在国内得到了广泛的应用,许多的政府主管部门、金融单位、企事业单位都使用了Notes以及在Notes上开发的各种办公系统,工作效率得到了极大的提高。
在实际的应用中,为了存档以及供没安装Notes系统的部门传阅,许多在Notes系统中流转的电子文档需要打印出来。不幸的是,Notes提供的打印功能很弱,一个文档只能按照给定表单的版式进行打印。但在实际的使用中,如政府部门,内容相同的一个文档,其上行公文和下行公文的版式是不一样的,这就需要将同一文档用多种样式打印。最直接的想法当然是在Designer中修改表单的版式,但由于应用系统一般是隐藏设计的,表单无法修改。还有就是最终用户的计算机水平有限,直接修改表单从技术上讲也行不通。
这时一个可行的做法就是:用VC++给用户提供一个“所见即所得”的编辑界面,并列出Notes文档中各部分的内容,让用户以拖放的方式将相关内容放到适当的位置上,同时还可以加入文字、图片等修饰内容,然后按照最终的版式在Notes外部直接生成一个Notes表单,并用此表单进行打印。这种方法既绕过了隐藏设计的障碍,又降低了对最终用户的技术要求。当然这一切都得益于Notes提供的API函数。
由于只需一个NSFItemScan函数就能收集到Notes文档中所有的域,而又有多种灵活的方式实现“所见即所得”的排版功能,因此在提出上述的思路后,本文将主要介绍如何构造Notes表单。

一、Notes表单结构简介
一个表单中有三个必需的域:$TITLE、$INFO和$BODY,辅助性的还有$FIELDS域及属性为placeholder的各域。
1.$TITLE域
$TITLE域的类型为TYPE_TEXT,其中保存表单的名称,Notes客户端窗口中“创建”菜单下列出的各表单名即为各表单note中$TITLE域的值。在Notes提供的C API头文件“stdnames.h”中有预定义的常量ITEM_NAME_TEMPLATE_NAME代表表单note的名称域,为保证程序的向后兼容,建议使用常量而避免直接使用$TITLE。
2.$INFO域
由于表单和文档的创建有关,$INFO域定义了通过此表单创建的文档的一些属性。实际上$INFO域中存储的是一个名为CDDOCUMENT的结构体,对生成文档属性的设定就是通过对该结构体中各分量的不同赋值实现的。结构体CDDOCUMENT 的定义及说明见Lotus C API 的参考文档。
$INFO域的类型为TYPE_COMPOSITE,对应的预定义常量为ITEM_NAME_DOCUMENT。
3. $BODY域
$BODY域是表单note中的核心域,整个表单显示和打印时的格式,还有通过此表单生成的文档所包含的域及其类型,都是在本域中定义的。由于$BODY域的结构非常复杂,本文将在第二部分专门介绍。$BODY域也是TYPE_COMPOSITE类型的,名称预定义常量为ITEM_NAME_TEMPLATE。
4. $FIELDS域
$FIELDS域是一个TYPE_TEXT_LIST类型的域,其中包含了用此表单生成的文档包含的所有域。但专为打印生成的表单中可以没有此域。
5. "placeholder"域
对$BODY域中定义的将来文档中要含有的每一个域,在表单中都对应一个类型为TYPE_INVALID_OR_UNKNOWN而标志为ITEM_PLACEHOLDER的域,域名和$BODY域中定义的一样,而其值为NULL。
标志为ITEM_PLACEHOLDER的域将被加入到“域名表”中,这样当用户选择了客户端中的“设计”菜单中的“视图”子菜单后,在弹出的对话框中选择“添加域”时,该域名才会被显示出来。
同样,这些域在打印的表单中不是必需的。

二、$BODY域详解
$BODY域中可以包含各种Notes对象,如文本、域、图像、热点、链接等,还有一些辅助性对象,如段定义、段引用等。为方便管理,所有这些对象的定义都是通过不同的结构体实现的。Notes中定义对象的结构体都以“CD”开头,如CDTEXT定义静态文本、CDFIELD定义域等,其他对象的具体定义请查阅Lotus C API 的参考文档。
通常,一个$BODY域的整体结构是这样的:
CDPABDEFINITION
CDPABDEFINITION
...
CDPARAGRAPH
CDPABREFERENCE
CDTEXT
text
...
CDPARAGRAPH
CDPABREFERENCE
CDBEGINRECORD
CDFIELD
CDBEGINRECORD
...
下面对其中的各部分分别予以说明。
1.段落预定义部分
CDPABDEFINITION定义页面上一个段落的属性,在这个结构体中我们可以定义段落的对齐方式、页边距、段间距、行间距等。在后面的某个具体段落中,如果定义了到此段定义的引用,则该段落就具有了此处定义的各属性。
段落的定义可以放在$BODY域的开头,也可以放在中间,只要保证序号PABID不重复就可以了。
2.静态文本的定义
上述总体结构的中间部分定义了一段文本:CDPARAGRAPH定义一段的开始,类似文本串中的一个回车换行符;CDPABREFERENCE定义一个到段定义的引用,从而本段就具有了前面定义的各种属性;CDTEXT是文本的头部,包含有文本的长度、字体、颜色等信息;text是实际的文本。
3.域的定义
对域的定义也是以CDPARAGRAPH和CDPABREFERENCE开始,但与文本不同的是,像域、图像等对象的定义,除了有作为头部的结构体外,还要有一对界定结构体CDBEGINRECORD和CDENDRECORD放在对象定义的前后两端。
有时在域的前面还要有一些提示性文字,如一个用于接收姓名的域name,通常在其前面要有“姓名”两个字,以便具体操作者知道此处要输入姓名。具体创建域时,这部分内容以文本形式放在CDBEGINRECORD之前,格式如上一步中所述。
在货币型或数值型的域中,为了对数据的格式进行更进一步的控制,在CDBEGINRECORD和CDFIELD中间还要插入一个CDEXT2FIELD结构,该结构提供了附加的格式定义。
域中的其他元素,如默认值计算公式、输入变换公式、域名、描述字串等放在CDFIELD后面,排列顺序和其长度值在CDFIELD结构体中的位置顺序一致。当然除域名外,其他元素如不是必要可以省略。
在本部分中,以文本和域为例,介绍了$BODY域中各对象的具体定义方式,其他对象与此类似。

三、创建Notes表单
在了解了Notes表单结构的基础上,通过API函数建立表单就很容易了。
首先打开一个数据库,然后在其中新建一个空白note,接下来就可以向其中添加各域了。像$TITLE这样的单一类型的域,可以直接调用NSFItemSetText函数创建。而像$INFO和$BODY这样的复合类型的域,就比较麻烦一些。通常的做法是,先申请一块足够大的内存,然后顺序写入各部分内容,最后调用NSFItemAppend函数创建域。
在向复合域中写入数据时,文本、域名等一般字符串可以直接写入,而各种结构体需调用ODSWriteMemory函数以Domino规范的形式写入,另外就是域定义中用到的各种公式,在写入前要经过NSFFormulaCompile变换。

四、例程
下面的程序段定义了一个带有默认值公式的名为“TextField”的域:

char TextFieldName[] = "TextField";
char TextDescription[] = "This is a Simple Text Field";
char TextDefValFormula[] = "/"Default/"";
char far *pBufferStart, far *pBuffer;
HANDLE hMem;
CDPABREFERENCE CDPabRef;
CDPARAGRAPH CDPara;
CDBEGINRECORD CDBegin;
CDENDRECORD CDEnd;
CDEXT2FIELD CDExt2Field;
CDFIELD CDField;
FONTIDFIELDS *pFontFields;

// 申请内存并锁定内存,获得指向该块内存的指针
OSMemAlloc (0, wCDBufferLength, &hMem);
pBufferStart = (char far *)OSLockObject(hMem);
memset( pBufferStart, 0, (size_t) wCDBufferLength );
pBuffer = pBufferStart;

// 填写 PARAGRAPH 结构
// 结构体的长度
CDPara.Header.Length = (BYTE) ODSLength(_CDPARAGRAPH);
// 结构体的类型
CDPara.Header.Signature = (BYTE)SIG_CD_PARAGRAPH;
// 转换为Domino规范的形式写入申请的内存
ODSWriteMemory( (void far * far *)&pBuffer, _CDPARAGRAPH, &CDPara, 1 );

// 填写 PABREF 结构
CDPabRef.Header.Signature = (BYTE)SIG_CD_PABREFERENCE;
CDPabRef.Header.Length = (BYTE) ODSLength(_CDPABREFERENCE);
// 要引用的段定义的序号
CDPabRef.PABID = wPabDefNumber;
ODSWriteMemory( (void far * far *)&pBuffer, _CDPABREFERENCE, &CDPabRef, 1 );

// 填写CDBEGINRECORD 结构
CDBegin.Header.Length = (BYTE)ODSLength(_CDBEGINRECORD);
CDBegin.Header.Signature = SIG_CD_BEGIN;
CDBegin.Version = 0;
CDBegin.Signature = SIG_CD_FIELD;
ODSWriteMemory( (void far * far *)&pBuffer, _CDBEGINRECORD,(void far *) &CDBegin, 1 );

// 填写CDEXT2FIELD 结构
memset(&CDExt2Field, 0, sizeof(CDEXT2FIELD));
CDExt2Field.Header.Length = (WORD)ODSLength(_CDEXT2FIELD);
CDExt2Field.Header.Signature = SIG_CD_EXT2_FIELD;
ODSWriteMemory( (void far * far *)&pBuffer, _CDEXT2FIELD, (void far *) &CDExt2Field, 1 );

// 填写CDFIELD 结构,定义文本域
CDField.Header.Signature = SIG_CD_FIELD;
CDField.Flags = FEDITABLE;
CDField.DataType = TYPE_TEXT;
CDField.ListDelim = LDDELIM_SEMICOLON;

// 本域中不用数值格式参数,全部清零
CDField.NumberFormat.Digits = 0;
CDField.NumberFormat.Format = 0;
CDField.NumberFormat.Attributes = 0;
CDField.NumberFormat.Unused = 0;

//本域中不用时间格式参数,全部清零
CDField.TimeFormat.Date = 0;
CDField.TimeFormat.Time = 0;
CDField.TimeFormat.Zone = 0;
CDField.TimeFormat.Structure = 0;

// 设定FontID
pFontFields = (FONTIDFIELDS *)&CDField.FontID;
pFontFields->Face = FONT_FACE_ROMAN;
pFontFields->Attrib = 0;
pFontFields->Color = NOTES_COLOR_BLACK;
pFontFields->PointSize = 14;

// 编译默认值公式
NSFFormulaCompile(NULL, 0, TextDefValFormula, (WORD) strlen(TextDefValFormula), &hTextDefValFormula, &wTextDefValFormulaLen, &wdc, &wdc, &wdc, &wdc, &wdc))

// 填写CDFIELD 结构的其余部分,因为DVLength值只有公式编译后才知道
CDField.DVLength = wTextDefValFormulaLen;
CDField.ITLength = 0;
CDField.TabOrder = 0;
CDField.IVLength = 0;
CDField.NameLength = strlen(TextFieldName);
CDField.DescLength = strlen(TextDescription);
CDField.TextValueLength = 0;
CDField.Header.Length = ODSLength(_CDFIELD) +CDField.DVLength +CDField.ITLength +CDField.IVLength +CDField.NameLength +CDField.DescLength +CDField.TextValueLength;

// 保证CDFIELD域长度为偶数
if (CDField.Header.Length % 2)
CDField.Header.Length++;
ODSWriteMemory( (void far * far *)&pBuffer, _CDFIELD, (void far *)&CDField, 1 );

// 获取指向编译后公式的指针
pTextDefValFormula = OSLock( char, hTextDefValFormula );
// 写入公式内容到内存
memcpy( pBuffer, pTextDefValFormula, wTextDefValFormulaLen );
pBuffer += CDField.DVLength;
// 解锁并释放公式占用的空间
OSUnlockObject(hTextDefValFormula);
OSMemFree(hTextDefValFormula);

// 域名部分,直接写入
memcpy( pBuffer, TextFieldName, CDField.NameLength );
pBuffer += CDField.NameLength;

// 域描述部分,直接写入
memcpy( pBuffer, TextDescription, CDField.DescLength );
pBuffer += CDField.DescLength;

// 保证整个域定义的长度为偶数
if ((pBuffer-pBufferStart) %2)
pBuffer++;

// 填写CDENDRECORD结构
CDEnd.Header.Length = (BYTE)ODSLength(_CDENDRECORD);
CDEnd.Header.Signature = SIG_CD_END;
CDEnd.Version = 0;
CDEnd.Signature = SIG_CD_FIELD;
ODSWriteMemory( (void far * far *)&pBuffer, _CDENDRECORD, (void far *) &CDEnd, 1 );

 

在web上显示视图的时候,总是不喜欢notes的默认界面,想换成表格的。用表格显示很容易,别让notes生成HTML,全部由你自己定制就成了。但如何交替用颜色显示不同行呢?就是说当view行数是寄数时显示一种颜色,偶数时显示另一种颜色。
原先想用@docnumber去做,但@docnumber产生的值时一个特殊值,无法转换成数字来判断。在www.lotus.com的开发者原地有一篇类似介绍,用的方法是把docnumber直接打印到html里,然后再用javascript来判断,这样虽然可以,但非常的麻烦。
下面有一种比较简便的方法:还是用javascript:
function transformView() {
var tableElements = document.getElementsByTagName('table') ;
var table = tableElements[tableElements.length - 1] ;
//上面是你的表格再页面中倒数第几个表格,如果你是倒数第2个,就-2
table.width = '100%' ;
table.cellSpacing = '0' ;
headers = table.getElementsByTagName("th") ;
for( i = 0; i < headers.length; i++) {
headers[i].bgColor = '#cccccc' ;
}
rows = table.getElementsByTagName("tr") ;
var counter = 0
for( i = 0; i < rows.length; i++) {
rows[i].bgColor = (i % 2 ? '#efefef' : '') ;
}
}
怎么样,是不是很cool?

如何将Domino服务器配置为Internet邮件服务器?
在学习配置之前,我们需要先简单了解一下协议的概念。
在进行Internet邮件收发的时候,我们需要使用到下面两个协议:
SMTP(简单消息传输协议)管理Internet邮件发送
POP3(邮局协议)管理Internet邮件接收
实际上两者的功能划分并不是如此清楚,也就是说两者间关系是相辅相成的,独立任何一方都不可能完成任务。
好了,协议就了解到这里,我们来看看最具体的东西,配置步骤:
1、配置——服务器配置文档——基本中,打开SMTP邮件邮递和SMTP侦听任务选项
2、在服务器配置文档——基本中,定义全限定的Internet主机名,也就是说用户的邮件地址会是:用户名@全限定Internet主机域名,比如用户admin,全限定的Internet主机名为www.flycat.net,那么该用户的邮件地址就是admin@flycat.net
3、将POP3加入NOTES.INI的ServerTasks参数中
4、配置——消息处理——网络域——添加网络域
5、确定网络域类型为全球网络域
6、确定网络域名称,任意都可以
7、确定网络域角色为R5 Internet网络域
8、确定为缺省全球网络域
9、在网络域文档中定义邮件地址转换规则
10、配置——消息处理——配置——添加配置
11、启动配置文档中的“在本地INTERNET网络域以外发送消息所用SMTP”
12、作好域名解析工作
13、重新启动服务器
14、用E-Mail客户端试试好了

原创粉丝点击