综合使用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 ""
End if
该段代码执行如下功能,判断如果当前搜索得到的文档集为空,就在浏览器端显示出错信息,然后将窗口路径指向某视图

再看一个代码片段:

flag =curdoc.save(True,False)
If flag Then
Print ""
Print "
"
Print ""
Print "

个人选项保存成功。

"
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.selected)field.options[field.options.length] = new Option(fieldEntryList.options.text);}}}function RemoveClick(field){if (field.length != 0){if (field.selectedIndex != -1){for(var i=0;i”按钮,代码如下: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.text}else{window.opener.document.forms[0].reader.value=window.opener.document.forms[0].reader.value+";"+fieldEntryList.options.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.getName().equals(CookieName))
  value = cookies.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:[email protected])
  上传基本原理:由页面发出一个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.bgColor = '#cccccc' ;
}
rows = table.getElementsByTagName("tr") ;
var counter = 0
for( i = 0; i < rows.length; i++) {
rows.bgColor = (i % 2 ? '#efefef' : '') ;
}
}
怎么样,是不是很cool?

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

LotusDomino环境下编写Web浏览器多数据库检索程序  
   随着计算机技术的日益普及,Internet/Intranet技术得到了更加广泛的应用,在世 界范围的大量Web网点上以及公司内部的Web服务器上,都存放着大量的信息。为使这些信 息得到的合理使用,Web服务器管理员应该为用户提供方便的信息检索方法。作为群件代 表的Lotus Domino 4.5集成了邮件处理、群件应用和Internet出版等功能,是Notes技术向Internet/ Intranet融合开放的里程碑式的产品。在继承以往产品全部优点与功能的基础上,Domino 将原先的Notes服务器提升为用途更广泛、使用更方便的Internet/Intranet服务器。在 Domino的帮助下用户能够快速 构建安全可靠的Web网点,开发出管理和控制Internet/Intranet的应用程序,并通过Web 浏览器实现对各类数据和服务器的交互式访问。Domino还为用户提供了多种检索信息的方 法,而且用户甚至不必编程。本文所要讨论的是通过简单编程实现在Web浏览器上对任意 多个数据库同时进行检 索的一种方法。   下面给出具体的实现方法:   1、创建数据库SiteSearch.NSF。   2、在数据库SiteSearch.NSF上创建Form名称为 SearchForm。   3、在SearchForm上创建Field,内容如下:   (1)DatabaseNames:类型为关键字,可编辑,允许多值,范围为:每行输入一个关 键字。关键字列表框内输入允许搜索的数据库列表。   假设有五个数据库允许用户检索,它们分别是:讨论板(Discuss.NSF)、规章制度 (Institution.NSF)、技术论文(TechDocuments.NSF)、公司最新动态( CompanyNews.NSF)、简报及公告(Bulletin.NSF),则在DatabaseNames关键字列表框内 可输入:   讨论板 | Discuss.NSF   规章制度| Institution.NSF   技术论文| TechDocuments.NSF   公司最新动态 | CompanyNews.NSF   简报及公告 | Bulletin.NSF   (2)SearchString:类型为文本,可编辑。   (3)$$QuerySaveAgent :类型为文本,显示时计算,数值为textSearchQuerySaveA gent,隐藏。   (4)SaveOptions:类型为文本,可编辑,默认值为“0”,隐藏。   4、创建代理TextSearchQuerySaveAgent。运行代理时间指定为:从“操作”菜单中 选择执行。指定代理操作的文档为:运行一次(可能使用@命令)。   代理程序的内容如下:   Sub Initialize    Dim ArrNsf As Variant    Dim item As NotesItem    '获得文档 context,因为此代理是一个$$QuerySaveAgent,所以$$QuerySaveAgent 可以访问 Context文档上的项目。    Dim sess As New NotesSession    Dim doc As NotesDocument    Set doc = sess.DocumentContext    '输出HTML页面的头部和标题:    Print {}    Print { }    Print {}    Print {}    Print "多数据库搜索"    Print {}    Print {}    '设置HTML页面的背景颜色:    Print {}    '获得欲检索的数据库,存放在数组ArrNsf内:    ArrNsf= doc.DatabaseName    '如果没有输入检索条件,则输出提示信息:    If Trim(Cstr(doc.Query(0)))="" Then    Goto EmptyQuery    End If    Dim MatchDoc As NotesDocument    Dim db As NotesDatabase,    Dim collection As NotesDocumentCollection    '下列循环从数组ArrNsf中取得欲检索的数据库进行处理:    For nsfCount=0 To Ubound( ArrNsf )    SearchDB=ArrNsf(nsfCount)    Set db = sess.GetDatabase("",SearchDB)    '出现错误则转向:    On Error Goto BadQuery    '获得满足查询条件的记录集:    Set collection = db.FTSearch(doc.Query(0),0)    On Error Goto 0    '输出数据库标题:    Print "


"    Print db.title    Print "
"    '输出该数据库中满足条件的记录个数:    Print "There are " & Str$(collection.Count) & " matching documents
"    '下列循环从满足条件的记录集中读取记录并输出:    For i% = 1 To collection.Count    Set MatchDoc = collection.GetNthDocument(i%)    Print {
}    Print ""    '如果文档包含Subject或Title,则输出Subject或Title:    Set item =MatchDoc.GetFirstItem( "Subject" )    If ( item Is Nothing ) Then    Set item = MatchDoc.GetFirstItem( "Title" )    End If    If Not (item Is Nothing) Then    Print " " & item.Name & ": " & item.Text    End If    '如果文档包含Categories,则输出Categories:    Set item = MatchDoc.GetFirstItem( "Categories" )    If Not (item Is Nothing) Then    Print item.Name & ": " & item.Text    End If    '输出文档产生日期和作者:    createDate = MatchDoc.Created    Print "Document create date:" &createDate    Print " Authors: "    Forall aAuthor In MatchDoc.Authors    Print aAuthor    End Forall    Print "
"    Next    Next   Out:    Print {}    Exit Sub   EmptyQuery:    Print {Search query can not be empty }    Resume Out   BadQuery:    Print {Query is not understandable: "} & doc.Query(0) & {"}    Resume Out   End Sub   在本例中使用的FTSearch搜索查询内容是一个符合 Notes 全文检索规则的字符串, 搜索内容可以是一个字词或多个字词。搜索内容可以包括 ? 和 * 通配符,并且可以由 ! (not)、& (and)、|(or) 操作符混合。所检索的数据库可以有全文索引,也可以没有全文索引。如果没有全文索引 ,则搜索速度会慢一些。运行本程序时,应先从浏览器中输入URL:http://homeURL/Site Search.NSF/SearchForm?OpenForm,然后选择欲检索的数据库并输入检索条件,单击 Submit按钮,则将列出所有 符合检索条件的文档。
版权所有2001

首页-->LOTUS文章-->LOTUS Scrpit编码指南
    编写公式的方法一个公式中含有一条或多条按顺序执行的语句。根据与公式相关联的对象和其他标准的不同,公式可以在所选的文档上运行一次或多次(每个文档运行一次)。除了从公式返回以外,公式中没有循环和控制跳转的语句。公式中对条件执行路径也有限制。代理公式将在选定的文档上多次地、有条件地执行。您可以:编写可得出计算结果的公式以下公式都应该有最后结果:复制公式 必须得出结果为真 (1) 或假 (0),并且可以应用到数据库的每个文档上。表单公式 必须得出表单名称。选择公式 必须得出结果为真 (1) 或假 (0),并且可以应用到视图中的每个文档上。列公式 必须得出一个可以转换成文本字符串的值。显示操作公式 必须得出真 (1) 或假 (0)。弹出式公式 必须得出一个文本串。窗口标题公式 必须得出一个文本或数字值,除非公式由一个任意类型的单独的域组成。区段存取公式 必须得出一个姓名或姓名列表。插入子表单公式 必须得出一个子表单的名称的文本值。区段标题公式 必须得出一个文本或数字值,除非公式由一个任意类型的单独的域组成。段落隐藏公式 必须得出真 (1) 或者假 (0)。缺省值公式 必须得出一个可以存放在当前域中的值。输入转换公式 必须得出一个可以存放在当前域中的值。缺省校验公式 必须得出是成功 (1) 还是失败 (0)。计算域公式 必须得出一个可以存放在当前域中的值。关键字公式 必须得出一个可以存放在当前域中的值或值的列表。这些公式可以简单到只有一个域、常量或者函数,也可以包含很多语句、使用临时变量、更改域的内容或者产生一些附加效果。但不论如何,其最后一个语句必须是一个合适的值。如果关键字 SELECT 没有显式指定的话,它会加在一个逻辑语句之前,这个逻辑语句用来终止复制或选择公式。这些公式运行于数据库(复制公式)或视图(选择公式)的每一个文档上,以确定复制和查看过程中包括或不包括哪些文档。函数 @All 返回一个真值因此公式“SELECT @All”将包括所有文档。样例:编写得出结果的公式1. 此样例是标准讨论数据库中“From”域的缺省值公式。它只包含一个返回用户姓名的函数。@UserName2. 此样例是一个标准讨论数据库中“Subject”域的输入校验公式。它含有一个 @If 函数,如果“Subject”是空的(用户未输入任何值),它将返回一个失败的值,反之则返回成功值。失败时还将显示一个消息。@If(Subject = ""; @Failure("You must enter a subject for your document."); @Success)3. 该样例是标准讨论数据库的“Memo”表单的窗口标题公式。它只有一个语句,但包含有嵌套的 @If 命令。如果当前文档是新的,则窗口标题是“New Memo”。如果当前文档已经存在,并有一个“Subject”域,而且该域非空,则窗口标题将取“Subject”域中的内容(如果文档带有附件,则标题前还会出现“>>”)。如果不存在“Subject”域或该域为空,则窗口标题为创建日期。@If(@IsNewDoc; "New Memo"; @If(@IsAvailable(Subject) & Subject != ""; @If(@Attachments; "* "; "") + Subject; @Text(@Created)))编写执行操作的公式下列公式不产生一个最后的可用结果,但可以改变域值和产生附加动作:便捷图标公式 触发时执行一次。代理公式 触发时在一个数据库上执行。一个代理公式运行在数据库中所选的每个文档上,这些文档由 UI 中指定的条件和公式中的 SELECT 关键字所决定。SELECT 关键字缺省是 SELECT @All。操作公式 触发时在一个视图或表单中执行。按钮公式 触发时在一个表单、导航器或 RTF 域中执行。操作热点 触发时在一个表单、导航器或 RTF 域中执行。样例:编写执行操作的公式该代理样例将一个基于“Main”表单的文档中的“Address”域的“Wayside Street”替换成“Wayside Drive”。有效的操作是 FIELD Address 赋值语句。如果 Address 中含有“Wayside Street”,则新的 Address 的内容是将字符串“Wayside Street”左边的字符,加上字符串“Wayside Drive”,然后再加上 Address 中“Wayside Street”右边的字符。否则,Address 将被重置为它的当前值。SELECT Form = "Main Form";ws := "Wayside Street";wd := "Wayside Drive";FIELD Address := @If(@Contains(Address; ws); @Left(Address; ws) + wd +@Right(Address; ws); Address)处理列表列表就是一个可以包含多个同类型值的命名实体。列表出现在以下几种情况:允许多值的域可能包含一个列表而非一个单值。某些函数返回一个列表。常量可以被指定为一个列表或者是单值。语法是多个单值用冒号分开;例如,"London":"New York":"Tokyo" 就是一个由三个元素构成的字符串列表常量。由于列表的连接运算具有最高的优先级,所以如果列表元素中的表达式是一个仅用于其自身的表达式的话,它就必须用括号括起来。例如,如果 3 为负数而 4 不是负数的话就应该写成 1:2-3):4,而不是 1:2:-3:4 。并列运算或交叉运算时的列表组合。列表限制了操作的范围,因为列表操作仅限于每一个列表元素。就象在一个循环中处理数组一样。以下函数是列表所特有的。函数 描述@Elements(list) 确定列表元素的数量。@Explode(string) 将文本字符串转换成一个文本列表。空格、逗号和分号用于分隔字符串的元素。@Explode(string; separator) 与上面的函数一样,但第二个参数指定了字符串中的元素的分隔符。@Explode(string; separator; empties) 与上面的函数一样,但是返回值为 @True ,因为第三个参数包含空的列表元素,其中出现了连续的分隔符。@Explode(date-range) 将一个日期范围转换成一个日期列表。参数必须是一个时间-日期值;返回值是文本列表。@Implode(list) 将文本列表转换成文本字符串,用空格分隔各个元素。@Implode(list; separator) 与上面的函数一样,但是第二个参数指定了字符串中每个元素之间的分隔符。@IsMember(string; list) 确定一个字符串是否为一个列表的成员。返回真 (1) 或假 (0)。@IsMember(list1; list2) 确定一个列表是否被包含在另一个列表中。返回真 (1) 或假 (0)。@IsNotMember(string; list) 确定一个字符串是否不是一个列表的成员。返回真 (1) 或假 (0)。@IsNotMember(list1; list2) 确定一个列表是否不被包含在另一个列表中。返回真 (1) 或假 (0)。@Keywords(list1; list2) 在 list1 中找到与 list2 中的单词相匹配的单词。单词分隔符有 " , ? ! ; : [ ] { } < >。@Keywords(list1; list2; separator) 与上面的函数一样,但第二个参数指定了单词的分隔符。@Member(value; list) 确定一个值在字符串列表中的位置。@Replace(list1; list2; list3) 将 list1 中与 list2 相匹配的值用 list3 中对应的值替换。@Subset(list; n) 从列表中提取出 n 个值。用 -n 表示从右到左提取。@Unique(list) 从字符串列表中删除重复的值。@Unique 返回一个随机的,唯一的文本值。样例:使用列表1 (并列运算)该样例用并列算法把两个数字列表相加。结果列表中有四个值 11、22、27 和 44。list1 := 10 : 20 : 30 : 40;list2 := 1 : 2 : (-3) : 4;list3 := list1 + list2;result := @Text(list1) + " + " + @Text(list2) + " = " + @Text(list3);@Prompt([OKCANCELLIST]; "Result"; ""; ""; result)2 (交叉运算)该样例用交叉算法将两个列表连接起来。结果列表中有 12 个值:Blue Sedan、Blue Coupe、Blue Van、Blue Truck、Red Sedan 等等最后是 Yellow Truck。cars := "Sedan" : "Coupe" : "Van" : "Truck";colors := "Blue" : "Red" : "Yellow";result := colors + " " *+ cars;@Prompt([OKCANCELLIST]; "Result"; ""; ""; result)3 (@Elements) 如果“Categories”域中没有元素,此例将显示一条消息,如果有的话则显示列表。@If(@Elements(Categories) = 0; @Prompt([OK]; "Categories"; "No categories");@Prompt([OKCANCELLIST]; "Categories"; ""; ""; Categories))4 (@Explode) 该样例使用缺省的分隔符空格、逗号和分号,把一个字符串分离成一个列表。结果列表中的值是:Paris、London、Chicago 和 Seoul。cityList := @Explode("aris London,Chicago;Seoul");@Prompt([OKCANCELLIST]; "List of cities"; ""; ""; cityList)5 (@Explode) 该样例使用分隔符逗号和分号将一个字符串常量分离成一个列表。结果列表中的值为:Paris、London、New York 和 Hong Kong。New York 和 Hong Kong 没有分离成 New、York、Hong 和 Kong,这是因为没有将空格作为分隔符。cityList := @Explode("aris,London,New York;Hong Kong"; ",;");@Prompt([OKCANCELLIST]; "List of cities"; ""; ""; cityList)6 (@Explode) 该样例在 London 和 New York 之间有一个空条目。如果第三个参数是 @False 或者被忽略,那么多个连续的分隔符将被看作一个。请确信逗号是连续的,没有被空格分隔。cityList := @Explode("aris,London,,New York;Hong Kong"; ",;"; @True);@Prompt([OKCANCELLIST]; "List of cities"; ""; ""; cityList)7 (@Implode) 该样例将一个列表常量组合到一个字符串变量中,用空格(缺省)作为分隔符。结果字符串中的值为:Minneapolis Detroit Chicago。city := "Minneapolis" : "Detroit" : "Chicago";cityString := @Implode(city);@Prompt([OK]; "Imploded string"; cityString)8 (@Implode) 该样例将一个列表常量组合到一个字符串列表中,用逗号和空格作为分隔符。结果字符串中的值为:Minneapolis, Detroit, Chicago。city := "Minneapolis" : "Detroit" : "Chicago";cityString := @Implode(city; ", ");@Prompt([OK]; "Imploded string"; cityString)9 (@Implode) 该样例使用冒号作为分隔符将一个列表域分离成一个字符串。如果 Categories 域中的输入是 Minneapolis, Detroit, Chicago 则结果为:Minneapolisetroit:Chicago 。@Prompt([OK]; "Categories"; @Implode(Categories; ":"))10 (IsMember) 该代理样例检查所选的文档,看 Categories 列表中是否有 Adjusted,如果有,Categories 将不变化。如果没有,Adjusted 将被添加到 Categories 列表中。FIELD Categories := @If(@IsMember("Adjusted"; Categories); Categories; @Explode(@Implode(Categories; ";") + ";Adjusted"; ";"));SELECT @All11 (@IsNotMember) 该样例检查所选文档,看 Categories 列表中是否有 Adjusted 和 Signed off。如果没有,则两者都被加入到 Categories 列表中。如果两者都已存在,则 Categories 维持原状。FIELD Categories := @If(@IsNotMember("Adjusted" : "Signed off"; Categories); @Explode(@Implode(Categories; ";") + ";Adjusted;Signed off"; ";"); Categories);SELECT @All12 (@Keywords) 该样例查找在 Cities 域中用到了哪个关键字。keywords := @Keywords(Cities ; "Paris" : "Moscow" : "Tokyo" : "Boston");@Prompt([OK]; "Keywords"; keywords)13 (@Member) 该样例让用户从一个列表中选择一个值,并显示该值在列表中的编号。cars := "Sedan" : "Coupe" : "Van" : "Truck";car := @Prompt([OKCANCELLIST] : [NOSORT]; "Cars"; "Pick one"; "Sedan"; cars);n := @Member(car; cars);@Prompt([OK]; "Your selection is ..."; "Number " + @Text(n))14 (@Replace) 该样例将 colors 列表中的“red”替换成“scarlet”并将“blue”替换成“turquoise”。colors := "red" : "blue" : "yellow" : "blue" : "black" : "red";from := "red" : "blue";to := "scarlet" : "turquoise";result := @Replace(colors; from; to);@Prompt([OKCANCELLIST] : [NoSort]; "Replacement list"; ""; ""; result)15 (@Subset) 该样例将 New Orleans, London 和 Frankfurt 放到 first3 中,并将 Singapore 和 Sydney 放到 last2 中。cities := "New Orleans" : "London" : "Frankfurt" : "Singapore" :"Sydney";first3 := @Subset(cities; 3);last2 := @Subset(cities; -2);@Prompt([OKCANCELLIST] : [NoSort]; "First three"; ""; ""; first3);@Prompt([OKCANCELLIST] : [NoSort]; "Last two"; ""; ""; last2)16 (@Unique) 该样例返回一个有四个元素的列表:red, blue, yellow 和 black。colors := "red" : "blue" : "yellow" : "blue" : "black" : "red";result := @Unique(colors);@Prompt([OKCANCELLIST] : [NoSort]; "Unique list"; ""; ""; result)使用条件语句@If 可以使您根据条件的真假执行不同的语句。这个条件通常是值和值的比较,但也可以是一个常量、一个变量或者一个函数的结果。例如:如果当前视图是“By Author”,则 @ViewTitle = "By Author" 为真。如果 Categories 至少有一个元素,@Elements(Categories) > 0 为真。1 用在条件中时表示真。@True 和 @Yes 都返回 1。如果条件为真,则比较运算和判断条件的函数返回 1 。0 用在条件中时表示假。@False 和 @No 都返回 0。如果条件为假,则比较运算和判断条件的函数返回 0 。@If 语句有奇数个参数,必须至少有三个,例如:条件为第一个参数,如果 @If 有多个条件的话,此后每隔一个参数就是一个条件。条件为真时,执行的是第二个参数中的语句,如果 @If 有多个条件,则第二个参数以后的每隔一个参数都是一个可执行的语句。条件为假时执行最后一个参数中的语句。最简单的 @If 语句有如下的形式:@If(condition; true statement; false statement)三个条件的 @If 语句有如下形式:@If(condition1; true1; condition2; true2; condition3; true3; false)@If 函数从左到右依次计算,遇到第一个为真的条件就执行对应的语句(也就是接下来的那个参数中的语句)。此后就不再执行任何 @If 中的语句了。如果没有一个条件为真,则执行最后一个参数中的语句。真值语句和假值语句的形式根据其前后关系的不同而有些变化:如果 @If 语句是公式中最后一条需要得出结果的语句,则真值和假值语句必须得出一个计算结果。如果 @If 语句是赋值语句右边的部分,则真值和假值语句必须能得出一个可以存放在左边的域或临时变量中的值。否则,真值和假值语句必须产生一个操作。@If 中的真值和假值语句不能包含一个赋值语句。赋值语句左边的部分只能出现在一个公式的最外层。以下语法形式是错误的:@If(condition; variable := value1; variable := value2)必须写成:variable := @If(condition; value1; value2)@If 语句是可以嵌套的。嵌套的 @If 语句对于解决公式语言有限的逻辑结构问题是很有帮助的,但它会导致复杂的语法表达。@Do 函数可以使您在一个条件下执行多条语句。样例:使用条件语句1. 该代理的例子将 Categories 中元素的数量与 0 比较。如果域中有元素,则不作任何改变(设置成自己的值)。如果域中没有元素,则设置成一个常量字符串。FIELD Categories := @If(@Elements(Categories) > 0; Categories; "To be supplied ...");SELECT @All2. 此窗口标题的样例首先检查 @IsNewDoc 的返回值。如果文档是新的,则窗口标题设置成文本常量“New Topic”。如果文档已经存在,并且当前视图是“AuthorView”的话,窗口标题设置为“Subject”域中的内容。否则,窗口标题就是“Subject”域的内容加上一个表示答复文档数目的字符串。StandardTitle := Subject + @DocDescendants(" (No Responses)"; " (1 Response)"; " (% Responses)");@If(@IsNewDoc; "New Topic"; @ViewTitle = "AuthorView"; Subject; StandardTitle) 编写消息和取得用户输入的方法您可以通过以下技术与用户进行交互:使用 @Prompt 编写消息用以下形式的 @Prompt 编写消息,与用户交流。@Prompt([OK]; title; prompt) 显示一个信息对话框,标题文本在对话框的顶部,提示文本在对话框的主体部分。@Prompt([OKCANCELLIST] : [NOSORT]; title; prompt; default; choices) 将显示一个对话框,标题文本在对话框的顶部,提示文本在对话框的主体部分,选择文本列表在提示文本的下面。这种形式的 @Prompt 主要用于取得输入信息,但也可以用于显示。最后一个参数必须是一个文本列表。prompt 和 default 可以为空。然而,如果该语句不是公式的最后一条语句,而且用户单击了“Cancel”的话,则接下来的公式便不再执行了。如果您想将列表排序的话就不要指定 [NOSORT] 。非文本值必须用 @Text 转换成文本才能用作 @Prompt 的参数。文本值可以是常量、临时变量、域或者表达式。样例:使用 @Prompt 编写消息1. 该样例在对话框中编写一个作为标题的文本常量和一个文本表达式。表达式是文本常量和已转换成文本的日期-时间值的组合。@Prompt([OK]; "Current time and date"; "The date is " + @Text(@Now; "D0S0") + ". " + "The time is " + @Text(@Now; "T0S1") + ".")2. 该样例编写一个文本常量作为标题。而对话框的内容是一个文本常量,后面跟一个多值域中的值。@Prompt([OKCANCELLIST] : [NoSort]; "Field offices"; "Current field offices are located in the following cities:"; ""; Field_offices)使用 @Prompt 和 @PickList 取得用户的输入用以下形式的 @Prompt 和 @PickList 交互的取得用户输入:@Prompt([YesNo]; title; prompt) 显示一个对话框,其中有标题文字、提示文本以及“是”和“否”按钮。如果用户单击“是”,则 @Prompt 返回真 (1),如果用户单击“否”,则返回假 (0)。@Prompt([YesNoCancel]; title; prompt) 显示一个对话框,其中有标题文字、提示文本以及“是”、“否”和“取消”按钮。如果用户单击“是”,则 @Prompt 返回真 (1),如果用户单击“否”,则返回假 (0),如果用户单击“取消”,则返回 -1 。@Prompt([OkCancelEdit]; title; prompt, default) 显示一个对话框,其中有标题文字、提示文本和一个用户可以键入信息的方框。@Prompt 将以文本值的形式返回用户的键入信息。如果用户单击“取消”,公式将立即终止。@Prompt([Password]; title; prompt) 与上面的公式作用一样,只是在用户键入时方框中显示 X ,而不是用户所输入的内容,这是出于保密的考虑。@Prompt([OkCancelList] : [NoSort]; title; prompt; default; choices) 将显示一个方框,标题文字在框的顶部,提示文本在框的主体部分,文字列表选项在提示文本的下面,并且突出显示缺省选项。最后一个参数必须是文本列表。@Prompt 将返回用户选择的列表元素。如果用户单击了“取消”,该公式将立即终止。如果您想让列表排序的话,请不要指定 [NoSort] 。@DbColumn 可以根据指定数据库中的当前视图的内容来生成一个列表。@Prompt([OkCancelCombo]; title; prompt; default; choices) 与上面的公式作用一样,但使用的是一个下拉式列表,框中缺省项目位于列表最顶部。@Prompt([OkCancelEditCombo]; title; prompt; default; choices) 与上面的公式作用一样,但是允许用户在列表上的方框中输入值或者从列表中选取一个值。@Prompt([OkCancelListMult]; title; prompt; default; choices) 与 OkCancelList 一样,但是允许用户选择多个列表元素并返回一个列表。@Prompt[(LocalBrowse]; title; filetype) 显示一个对话框,允许用户从本地文件系统中选择名称。标题文本位于对话框的顶部,在对话框主体部分的左边显示一个选择文件的列表框,在它下面是一个文件类型的列表框,在右边是一个选择目录的列表框。对话框中包含“选择”、“取消”和“网络”或“帮助”按钮。filetype 参数是一个文本值,从数字 1 到 3,指定初始化显示的文件类型:"1" 表示 .nsf 文件,"2" 表示 .ntf 文件;"3" 表示全部文件。@PickList([Custom]:[Single]; server : file; view; title; prompt; column) 显示一个对话框,其中包含标题文本、提示文本以及选项列表。列表中是指定数据库的视图。用户可以选择一个(如果指定了 [Single])或任意数量的元素(如果未指定 [Single])。@PickList 返回所选列表元素的指定列中的值。这有点象用 @DbColumn 来生成一个列表给 @Prompt 的过程。@PickList([Name]:[Single]) 与上面公式的作用类似,只是数据库是一个通讯录,而视图是“个人”视图。用户可以在 @PickList 对话框中选择通讯录。当把非文本值用作 @Prompt 和 @PickList 的参数时,必须先通过 @Text 转换成文本。文本值可以是常量、临时变量、域或者表达式。如果返回值要用作非文本值的话,它还必须再加以转换。样例:使用 @Prompt 和 @PickList 取得用户的输入1 (YesNo) 这个输入校验公式向用户查询的 TotalAmount 域。如果用户单击“否”,则出现一个错误消息。@If(@Prompt([YesNo]; "Is this total within budget?"; @Text(TotalAmount; "C")); @Success; @Failure("Total not within budget"))2 (OkCancelEdit) 此按钮公式要求用户指定一个服务器和数据库,并打开此数据库。如果用户没有向编辑框中输入信息而直接单击“是”,则返回值是一个空字符串。server := @Prompt([OkCancelEdit]; "Server"; "Enter the name of a server"; "");database0 := @Prompt([OkCancelEdit]; "Database"; "Enter the name of a database on " + @If(server = ""; "your workstation"; server); "");database := @If(@Contains(database0; "."); database0; database0 + ".nsf");@Command([FileOpenDatabase]; server : database)3 (Password) 这个域输入校验公式从用户那里取得一个口令。并将此口令与文档中“Password”域的内容相比较。pass := @Prompt([Password]; "Password"; "What is the password?");@If(pass = Password; @Success; @Failure("Password incorrect"))4 (OkCancelList) 此便捷图标公式向用户展示数据库目录中的数据库列表,并打开用户所选的数据库。第一个 @DbColumn 将 Databases by _Replica ID 视图的第 4 列的值的列表放入临时变量 titles 中。第二个 @DbColumn 将 Databases by _Replica ID 视图的第 2 列的值的列表放入临时变量 servers 中。第三个 @DbColumn 将 Databases by _Replica ID 视图的第 3 列的值的列表放入临时变量 databases 中。临时变量 list 将 titles,servers 和 databases 组合起来在 @Prompt 中呈现给用户。然后,该公式将 @Prompt 的返回值分解成服务器和数据库的名称以用在 FileOpenDatabase 命令中。titles := @DbColumn(""; "doc":"catalog.nsf"; "Databases by _Replica ID"; 4);servers := @DbColumn(""; "doc":"catalog.nsf"; "Databases by _Replica ID"; 2);databases := @DbColumn(""; "doc":"catalog.nsf"; "Databases by _Replica ID"; 3);list := titles + " *-* " + servers + " *:* " + databases;member := @Prompt([OkCancelList]; "Open Database"; "Select a database"; ""; list);server := @Left(@Right(member; " *-* "); " *:* ");database := @Right(member; " *:* ");@Command([FileOpenDatabase]; server:database)5 (OkCancelListMult) 此按钮公式向用户展示了一个部门名称及销售总额的列表。用户可以从列表中选择任意数量的元素,公式计算其总和。departments := @DbColumn(""; "" : "sales.nsf"; "Main View"; 1);totalSales := @DbColumn(""; "" : "sales.nsf"; "Main View"; 2);totalsList := @Text(totalSales; "C") + " " + departments;sumList := @Prompt([OkCancelListMult]; "Total sales by department"; "Select the ones you want to sum"; ""; totalsList);sum := @Sum(@TextToNumber(sumList)); @Prompt([OK]; "Sum"; @Text(sum))6 (Custom) 此按钮公式向用户展示 DOC 服务器上的 CATALOG.NSF 中的 Databases by Replica ID 视图。用户从列表(视图)中选择一个元素(行),此公式将打开该行中第 3 列所指定的数据库。name := @PickList([Custom]; "doc" : "catalog"; "Databases by Replica ID"; "Open database"; "Select a database that is on server Doc"; 3);@Command([FileOpenDatabase]; "doc" : name)使用 @DialogBox 填写表单@DialogBox 将在一个对话框中显示一个设计的表单,其中有“确定”和“取消”按钮。当用户单击“确定”时,对话框中的某个域的内容将被传递到当前文档同名的域中。@DialogBox 不传递 RTF 文本。在设计时要注意不能包含 RTF 文本。出现在对话框中的表单最好使用一个区域布局来创建,而且 @DialogBox 应该使用[AutoVertFit] 和 [AutoHorzFit] 选项。样例:使用 @DialogBox 填写表单假如您有一个包含很多域的表单,但用户通常在创建文档时只填写其中很少的几个域。这时您可以创建另一个叫做“Dialog”的隐藏表单,在这个表单中包含一个区域布局,其中有用户通常填写的域。在主表单的最上面,您可以创建一个包含下列公式的按钮。当用户单击此按钮时,此公式将使用“Dialog”表单显示一个对话框。用户可以在对话框中填写域然后单击“确定”,于是填写的内容便被传递到主表单中。@DialogBox("Dialog"; [AutoVertFit] : [AutoHorzFit])取得和设置环境变量您可以从 NOTES.INI 文件中设置和提取环境变量(Windows,OS/2 和 UNIX 都适用)。@SetEnvironment(variable; value) 将一个已命名的变量设置成指定的值。您也可以用 ENVIRONMENT 关键字和双参数形式的 @Environment。@Environment(variable) 提取一个已命名变量的值。环境变量的值是文本。非文本值必须在设置值之前和提取值之后转换成文本。用户环境变量是以 $ 字符开头的。如果您用编辑器或者 LotusScript 添加一个环境变量,此后您还想用 @Environment 提取的话,那么它的第一个字符必须是 $。请了确信您已经清楚了您的公式将会影响到哪个 NOTES.INI 文件。如果公式在一个服务器数据库中,则以下几种情况下公式是运行在服务器上的:复制公式、由“当一个新邮件到来时执行”和定时执行触发的代理、选择公式或者列公式。否则,公式将运行于用户的工作站上。复本拷贝将根据其所在的服务器或工作站决定访问不同的 NOTES.INI 文件。服务器访问服从于管理限制(请参阅在“Notes 管理帮助”中的“限制服务器上的代理” 或者“管理员指南”)。环境变量的作用有:在不同的公式和数据库之间传递临时数据。为一个用户生成连续的数字。样例:取得和设置环境变量1. 该样例将一个数字转换成文本并将其保存在一个环境变量中。ENVIRONMENT OrderNumber := @Text(NewOrderNumber);以下公式是等效的。@Environment("OrderNumber"; @Text(NewOrderNumber);@SetEnvironment("OrderNumber"; @Text(NewOrderNumber);2. 该样例提取一个环境变量的值,并将其转换成一个数字并存放在局部变量中。OldOrderNumber := @TextToNumber(@Environment("OrderNumber");3. 该样例适用于“创建时计算”域。该公式保留有一个叫“OrderNumber”的环境变量。每当创建了一个新文档时,此公式从环境变量中取得该值然后加一。这种计算法对于一个要进行复制的数据库是不适用的,数据库必须存在于单个服务器或工作站上,而且公式必须运行在相同的机器上。OldOrderNumber := @Environment("OrderNumber");NewOrderNumber := @TextToNumber(@If(OldOrderNumber = ""; "0"; OldOrderNumber)) + 1;ENVIRONMENT OrderNumber := @Text(NewOrderNumber);NewOrderNumber;4. 第一个公式被每个销售人员运行一次。该公式提示用户输入销售地区,并将其存放在一个叫“SalesArea”的环境变量中。在销售文档的表单中,您将第二个公式用作 SalesArea 域的缺省值公式。该公式从环境变量中取得值。这样销售人员便不再需要为每个新文档输入销售地区的信息了,而且也避免了在缺省值公式中硬性规定一个销售区域。ENVIRONMENT SalesArea := @Prompt([OKCANCELLIST]; "Sales Area"; "What is your sales area?"; "Central"; "East" : "Central" : "West");@Environment("SalesArea");处理错误的方法错误有两种类型:语法错误当您确认或退出正在编写的公式时可能会出现语法错误的提示。您将接到一条指定错误的消息,有错误的行被突出显示出来。必须查出错误并改正它。通常,语法错误包括:函数名称或关键字拼写错误缺少括号或括号多余字符串常量两边缺引号语句之间没有分号您最好在写公式时随时检查。多重嵌套可能会使公式的语法非常复杂。运行错误运行错误在公式运行的时候发生。这种错误可分为以下几类:无法预料的错误 — 这是在开发时出现的错误,您的用户是看不见的。例如,如果遗漏了函数的某个参数,则在运行时将出现以下消息:“函数的参数不够”。应该测试公式尽量修正所有无法预料的错误。不予汇报的错误 — 这些是错误的结果,但是并不当作错误汇报。例如,如果试图用 @Prompt 来显示一个数字值,@Prompt 可以运行,但显示为空白。您的用户永远也看不见这些错误。应该测试公式,确保所有结果都是正确的。可以预料的错误 — 这些错误可能是用户在运行公式时造成的。例如,在提示输入一个数据库的名称时,用户输入了一个并不存在的数据库名称。无法阻止这些错误的发生但可以预计得到,或者可以有意测试一下,然后给出适当的解决办法。以下函数可以帮助您处理运行错误:@IsError(value),当它发现一个域、临时变量或者表达式中包含错误时就返回 1 。@Error 生成一个错误。@Failure(message),在输入校验公式中用来显示消息。@Success 总是返回 1。@Return(value) 终止公式的运行并返回一个值。当一个域内嵌的校验检查失败时,Notes 将产生一个错误。例如,如果将一个域指定为数字型的,而用户输入的是一个非数字型的值,Notes 将把该域的值设置为错误。您也可以设置一个域值为 @Error 来生成错误。Notes 在用户试图保存文档时汇报域中的错误。例如,如果一个数字域包含一个非数字的值,那么当用户试图保存文档时,Notes 将产生一条消息“不能将文本转换为数字”。为了在错误发生时改变消息或执行另一个操作,请在域的校验公式中使用 @IsError 来检查域中的错误。可以用 @Failure 生成您自己的消息,但仅仅只是在域校验公式中可以这样做。为了在一个域中使用您自己的出错条件,请在域转换公式中测试域的值,并在检测出一个错误条件时返回一个 @Error。除了域公式以外,例如在代理、按钮或者热点中,可以检查域的内容并对错误条件立即作出反应。例如,如果您用一个按钮来检查域,就可以在用户保存文档之前修改域值或者报告错误。在检查错误时,值得注意的是,内嵌的校验机制在用户输入值时马上就可以汇报错误,而使用 @Error 的转换公式直到用户保存文档时才报告错误。样例:运行错误1.如果 Price 域中有错误的话,这个校验公式将使该文档保存失败,并显示一条指定的错误信息。@If(@IsError(Price); @Failure("The Price field must be numeric"); @Success)2. 此样例包含输入转换和校验公式。如果 Price 域的值大于 14.99 的话,输入转换公式将在 Price 域中放置一个错误。如果发现此错误,输入校验公式它就会使文档的保存操作失败,并显示一条指定的错误信息。REM "Input translation formula for Price field";@If(Price > 14.99; @ERROR; Price)REM "Input validation formula for Price field";@If(@IsError(Price); @Failure("The Price field must be numeric and 14.99 or less"); @Success)3. 此按钮公式在对 Price 打折扣之前先检查它是否有错误。如果该域有错误,则该公式返回一个错误信息。FIELD Price := @If(!@IsError(Price) & @IsNumber(Price) & Price < 15; Price * 0.85; @Return(@Prompt([OK]; "Error in price field"; "Must be numeric and less than 15.00"; "")));@All4. 该按钮公式检测 Price 域中是否有错误。如果该域含有错误,则此公式将该域设置成一个通过 @Prompt 从用户那里得到的值。FIELD Price := @If(!@IsError(Price) & @IsNumber(Price) & Price < 15; Price; @TextToNumber(@Prompt([OKCANCELEDIT]; "Error in price field"; "Enter a number under $15.00"; "")));@All使用函数所有函数都能计算出一个值,并且可以放在公式中任何允许此类型值的地方。当公式执行的时候,公式的值代替了公式的位置。有些公式也有附加效果。例如,@Prompt 会产生一个消息框。大多数函数可以用于任意 Notes 对象的公式中,但有一些函数却被限制在适当的范围内。以下表格列举了受限的函数,并列举了它们可以用在哪些对象中。此外,您必须进入相应的上下文中才能使函数返回有关当前数据库、视图、文档或者域的信息,也就是说,那些对象必须是当前的。受限函数 函数只应用于这些 Notes 对象@All 复制公式、代理、视图选择公式@AllChildren 复制公式、视图选择公式@AllDescendants 复制公式、视图选择公式@Command 便捷图标公式、手动代理、按钮、操作热点@DbColumn (Notes 数据库) 便捷图标公式、操作、按钮、热点、域设计、代理(除邮件以外)@DbLookup (Notes 数据库) 便捷图标公式、操作、按钮、热点、域设计、代理(除邮件以外)@DeleteDocument 代理@DeleteField 代理@DocChildren 列公式、窗口标题公式@DocDescendants 列公式、窗口标题公式@DocLevel 列公式、窗口标题公式@DocMark 代理@DocNumber 列公式、窗口标题公式@DocParentNumber 列公式、窗口标题公式@DocSiblings 列公式、窗口标题公式@Failure 域校验公式ENVIRONMENT 除弹出式热点公式以外的所有场合@Environment 除了正在编写的弹出式热点公式以外的场合FIELD 便捷图标公式、代理、按钮、操作热点、域设计@IsCategory 列公式@IsDocBeingLoaded 表单设计、域设计@IsDocBeingMailed 按钮、热点、域设计@IsDocBeingRecalculated 按钮、热点、域设计@IsDocBeingSaved 按钮、热点、域设计@IsExpandable 列公式@IsNewDoc 便捷图标公式、按钮、窗口标题公式、表单设计、域设计@MailSend 便捷图标公式、代理、按钮、操作热点@PickList 便捷图标公式、手动执行的代理、按钮、操作热点、域设计@Platform 便捷图标公式、手动执行的代理、按钮、热点、除选择公式和列公式以外的视图设计、表单设计、域设计@Prompt 便捷图标公式、手动执行的代理、按钮、操作热点、域设计@Responses 窗口标题公式、域设计@Return 便捷图标公式、代理、按钮、热点、域设计SELECT 复制公式、代理、视图选择公式@SetDocField 便捷图标公式、代理、按钮、操作热点、域设计@SetEnvironment 除了弹出式热点公式以外的所有场合@SetField 便捷图标公式、代理、按钮、操作热点、域设计@Success 校验公式@Unavailable 代理@ViewTitle 代理使用命令命令就是一些可以用户界面中执行即时操作的特殊函数。大多数命令是模仿与菜单命令的。例如以下公式,如果从按钮执行,就是将当前文档置于编辑模式并将插入点下移两次:@Command([EditDocument]; "1");@Command([EditDown]; "2")命令的语法表示如下:@Command([command-name]; arg1; arg2; ... argn)@PostedCommand([command-name]; arg1; arg2; ... argn)函数名称是 @Command 或者 @PostedCommand。第一个参数是用方括号括起来的命令名称。在帮助导航器中选择“Scripts & formulas”,然后选择“Notes 公式语言命令 A-Z”即可得到一个命令的列表,或者请参阅“程序员指南”的第八章。其他则是命令所带的参数。您不能在一个不与用户交互的公式中使用命令,如定时代理、域公式或一个后台宏。您不能在 Web 应用程序中使用大多数的命令,因为它们是基于 Notes 工作站用户界面的。在 Web 应用程序中只能够使用以下命令(在各个命令的描述中分别注明了它的限制):[CalendarFormat]、[CalendarGoTo]、[Compose]、[EditClear]、[EditDocument]、[EditInsertFileAttachment]、[FileCloseWindow]、[FileOpenDatabase]、[OpenDocument]、[FileSave]、[NavigateNext]、[NaviagtePrev]、[NavigateNextMain]、[NavigatePrevMain]、[OpenNavigator]、[OpenView]、[ToolsRunMacro]、[ViewChange]、 [ViewExpandAll]、ViewCollapseAll] 和 [ViewShowSearchBar]、[ViewRefreshFields] 和 [FileOpenDBRepID]。@Command 函数按顺序和其他函数一起执行,但有一些例外。例如,以下公式就是先执行命令:@Command([EditDocument]; "1");@Prompt([OK]; "Edit mode"; "The document is now in edit mode.")@PostedCommand 函数将在所有其他函数执行完毕后才按顺序执行。这是为了模仿 Notes R3 中的 @Command。例如,以下公式将在最后才执行命令:@PostedCommand([EditDocument]; "1");@Prompt([OK]; "Edit mode"; "The document will go into edit mode.")您可以检查 @Command 的返回值(但 @PostedCommand 不行),并对其作出响应。如果命令执行正确返回值为 @True ,否则返回值为 @False 。以下便捷图标的公式样例在命令 FileOpenDatabase 执行失败时返回。@If(@Command([FileOpenDatabase]; "NEWSUBJ"); ""; @Return(""));@Command([Compose]; ""; "Main Topic");@Command([EditGotoField]; "Subject");@Command([EditInsertText]; "New subject");@Command([EditGotoField]; "Body")执行字符串操作的方法公式语言函数支持数据类型转换、字符串长度、字符串比较、子串、大小写和修剪等操作。您可以:转换数据类型在对数据操作时类型必须是正确的。以下的函数用来转换数据和测试数据类型。函数 描述@Text(value) 将一个值转换为文本字符串。@Text(value; format) 根据指定的格式将一个数字或时间-日期值转换成文本字符串。@TextToNumber(string) 将文本字符串转换成一个数字。@TextToTime(string) 将文本字符串转换成一个日期-时间值。@IsText(value) 如果值是文本字符串或文本字符串列表,则返回真值 (1) 。@IsNumber(value) 如果值是数字或数字列表,则返回真值 (1) 。@IsTime(value) 如果一个值是时间-日期或时间-日期列表,则返回真值 (1)。@Char(number) 将一个 IBM 代码页 850 代码转换为对应的字符。样例:转换数据类型1 (@TextToNumber, @Text)。该样例使用 @Prompt 交互的读取一个数字,计算其对数,并用 @Prompt 交互的显示答案。由于 @Prompt 只能处理文本值,所以输入的值 i 在进行对数计算之前必须转换成数字,答案 n 必须在显示之前必须转换成文本字符串。i := @Prompt([OKCANCELEDIT]; "@Log Test"; "Enter a number"; "");n := @Log(@TextToNumber(i));@If(@IsError(n); @Return(@Prompt([OK]; "@Log Test"; i + " is not a number")); ""); @Prompt([OK]; "@Log Test"; "The logarithm of " + i + " is " + @Text(n))2 (@Text)。该样例根据日期-时间格式 T1S1 将 @Now 返回的日期-时间值转换成文本。T1 仅指时间,S1 仅指小时和分钟。在晚上六点时,用户可以看到一个信息框,其中的文字以“The time is ...”开头,后面是“06:00 PM”。@Prompt([OK]; "The time is ..."; @Text(@Now; "T1S1"))3 (@Text)。该样例根据数字格式 C,2 将数字 800 转换成文本并予以显示。C 表示货币格式(以美元符开头),2 表示保留两位小数。用户看到的信息框中的文字以“800 dollars”打头,后面是“$800.00”。@Prompt([OK]; "800 dollars"; @Text(800; "C,2"))4 (@Text, @TextToTime)。此样例将文本字符串“Today”转换成当天的日期,然后将此日期转换成文本供 @Prompt 使用。例如,如果今天是 1995 年 7 月 8 日,用户将看到一个信息框,其中的文字以“Today's Date”开头,后面跟着“07/08/95”。@Prompt([OK]; "Today's Date"; @Text(@TextToTime("Today")))5 (@IsNumber)。此样例将域 A 和 B 相加。如果某一个域不是数字型的话,它将被转换成数值 0。因为用户无法在一个数字域中输入非数字的值,所以如果一个数字域是空的,并且没有缺省值公式的话,它的缺省值就是一个空的文本字符串。此公式中的 @IsNumber 函数就避免了这个情况。@If(@IsNumber(A); A; 0) + @If(@IsNumber(B); B; 0)6 (@Char)。此样例将整数 65 转换成大写字符 A。@Prompt([OK]; "IBM Code Page 850 code 65"; @Char(65))连接、比较和判定长度+ 运算符用来连接字符串。=、<>、!=、=!、><、<、>、<= 和 >= 运算符用来比较字符串。以下的函数用来判定字符串的长度和比较字符串:函数 描述@Length (string) 返回字符串的长度,以字符为单位。@Length(stringlist) 返回字符串列表中每个元素的长度,以字符为单位。@Matches (string; pattern) 判定两个字符串是否匹配。可以用通配符来扩展比较的范围。@Like (string; pattern) 判定两个字符串是否匹配。遵循 ANSI SQL 标准。@Like(string; pattern; esc) 与上面的一样只是多了一个转义字符。@Matches 用 ? 来匹配任意一个单独的字符,用 * 来匹配任意的字符序列,此序列中也可以包括其他的通配符。@Like 使用 _(下划线)来匹配任意一个单独的字符,用 %(百分号)来匹配任意的字符序列,符合 ANSI X3.135-1992 标准。@Matches 使用 作为转义字符。样例:连接、比较和判定长度1 (+)。该样例连接两个字符串以形成 ABCDEF。@Prompt([OK]; "Concatenation"; "ABC" + "DEF")2 (+)。该样例连接两个输入的字符串。Input1 := @Prompt([OKCANCELEDIT]; "Concatenation - first element"; "Enter any text in the box"; "ABC");Input2 := @Prompt([OKCANCELEDIT]; "Concatenation - second element"; "Enter any text in the box"; "DEF"); @Prompt([OK]; "Concatenation - result"; Input1 + Input2)3 (=)。如果两个输入字符串相等,该样例为 YesNo 返回 True,如果不等的话则为 YesNo 返回 False。Input1 := @Prompt([OKCANCELEDIT]; "Comparison - first element"; "Enter any text in the box"; "ABC");Input2 := @Prompt([OKCANCELEDIT]; "Comparison - second element"; "Enter any text in the box"; "DEF");YesNO := @If(Input1 = Input2; "The strings are equal"; "The strings are not equal"); @Prompt([OK]; "Comparison - result"; YesNo)4 (@Length)。该样例显示 9,即字符串 abcdefghi 的长度。@Prompt([OK]; "Length of abcdefghi"; @Text(@Length("abcdefghi")))5 (@Length)。该样例创建一个数字列表。该列表中的每个元素包含 TextList 中相应元素的长度。@Length(TextList)6 (@Matches)。如果输入 abc,则该样例在 YesNo 中返回 True。Input := @Prompt([OKCANCELLIST]; "@Matches Input"; "Choose one"; "abc"; "abc" : "bcd" : "cde" : "xyz" : "123");YesNo := @If(@Matches(Input; "abc"); " matches abc"; " does not match abc"); @Prompt([OK]; "@Matches Result"; Input + YesNo)7 (@Matches). 该样例为 YesNo 返回 True,如果输入的每个字符都是字母的话,也就是说,从 a 到 z 的范围内。集合 {a-z} 指定字母的范围,前面的 + 表示任意多个字母。Input := @Prompt([OKCANCELLIST]; "@Matches Input"; "Choose one"; "abc"; "abc" : "bcd" : "cde" : "xyz" : "123");YesNo := @If(@Matches(Input; "+{a-z}"); " matches +{a-z}"; " does not match +{a-z}"); @Prompt([OK]; "@Matches Result"; Input + YesNo)8 (@Matches)。如果每个输入的字符都不是字母,也就是说,在集合 {a-z} 之外,则该样例为 YesNo 返回 True。{!a-z} 表示不在集合 a 到 z 中。前面的 + 表示任意多个字母。Input := @Prompt([OKCANCELLIST]; "@Matches Input"; "Choose one"; "abc"; "abc" : "bcd" : "cde" : "xyz" : "123");YesNo := @If(@Matches(Input; "+{!a-z}"); " matches +{!a-z}"; " does not match +{!a-z}"); @Prompt([OK]; "@Matches Result"; Input + YesNo)9 (@Matches)。如果输入包含由任意数目字符包围的连续的 bc 字符,则该样例为 YesNo 返回 True。Input := @Prompt([OKCANCELLIST]; "@Matches Input"; "Choose one"; "abc"; "abc" : "bcd" : "cde" : "xyz" : "123");YesNo := @If(@Matches(Input; "*bc*"); " matches*bc*"; " does not match *bc*"); @Prompt([OK]; "@Matches Result"; Input + YesNo)10 (@Matches)。如果输入是以 a 开始并且有三个字符长,或者输入以 1 开始并且有三个字符长,则该样例为 YesNo 返回 True。Input := @Prompt([OKCANCELLIST]; "@Matches Input"; "Choose one"; "abc"; "abc" : "bcd" : "cde" : "xyz" : "123");YesNo := @If(@Matches(Input; "a??|1??"); " matches a??|1??"; " does not match a??|1??"); @Prompt([OK]; "@Matches Result"; Input + YesNo)11 (@Like)。该代理检查每个文档中 textBody 域内的两个字符串集合。第一个字符串是顺序包含“acquisition”和“Acme”的任何文本。第二个字符串是顺序包含“Acme”和“51%”的任何文本。第二个 @Like 语句使用斜杠 (/) 作为转义字符,因而可以指定百分号 (%)。不要使用反斜杠 () 作为转义字符。@If(@Like(textBody; "%acquisition%Acme%"); @Prompt([OK]; "Found reference to "acquisition""; Subject); "");@If(@Like(textBody; "%Acme%51/%%"; "/"); @Prompt([OK]; "Found reference to "51%""; Subject); "");SELECT @All查找并提取子串以下函数查找并提取子串:函数 描述@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,其中单词是在指定的分隔符之间的文本。样例:查找并提取子串1 (@Contains)。如果 Substring 在 String 中的某个地方出现,则该样例为 R 返回 True。该搜索是区分大小写的。String := @Prompt([OKCANCELEDIT]; "String"; "Enter a string"; "");Substring := @Prompt([OKCANCELEDIT]; "Substring"; "Enter a beginning substring"; "");Yes := Substring + " is in " + String;No := Substring + " is not in " + String;R := @Contains(String; Substring);@If(R; @Prompt([OK]; "Yes"; Yes); @Prompt([OK]; "No"; No))2 (@Contains)。如果 Substring1 或 Substring2 在 String 的某个地方出现,则该样例为 R 返回 True。该搜索是区分大小写的。String := @Prompt([OKCANCELEDIT]; "String"; "Enter a string"; "");Substring1 := @Prompt([OKCANCELEDIT]; "Substring"; "Enter substring 1"; "");Substring2 := @Prompt([OKCANCELEDIT]; "Substring"; "Enter substring 2"; "");Yes := Substring1 + " or " + Substring2 + " is in " + String;No := Substring1 + " and " + Substring2 + " are not in " + String;R := @Contains(String; Substring1 : Substring2);@If(R; @Prompt([OK]; "Yes"; Yes); @Prompt([OK]; "No"; No))3 (@Left)。该样例为 R 返回 String 中最左边的 N 个字符。String := @Prompt([OKCANCELEDIT]; "String"; "Enter a string"; "");Number := @Prompt([OKCANCELEDIT]; "Number of characters"; "Enter a number of characters"; "");N := @TextToNumber(Number);R := @Left(String; N); @Prompt([OK]; "Leftmost characters"; R)4 (@Left)。该样例为 R 返回 String 中的 Substring 左边的字符。String := @Prompt([OKCANCELEDIT]; "String"; "Enter a string"; "");Substring := @Prompt([OKCANCELEDIT]; "Substring"; "Enter a substring"; "");R := @Left(String; Substring); @Prompt([OK]; "Characters left of " + Substring; R)5 (@RightBack, @Left)。如果在 ComposedBy 域中的公共名是“Judith Woo”,本公式计算出“Woo, Judith”。@RightBack 返回姓,@Left 返回名。@RightBack(@Name([CN]; ComposedBy); " ") + ", " + @Left(@Name([CN]; ComposedBy); " ")6 (@Middle)。该样例为 R 返回 String 中从 Substring 以后开始的 N 个字符。String := @Prompt([OKCANCELEDIT]; "String"; "Enter a string"; "");Substring := @Prompt([OKCANCELEDIT]; "Substring"; "Enter a substring"; "");Number := @Prompt([OKCANCELEDIT]; "Number of Characters"; "Enter the number of characters"; "");N := @TextToNumber(Number);R := @Middle(String; Substring; N); @Prompt([OK]; Number + " characters starting after " + Substring; R)7 (@ReplaceSubstring)。此代理样例对所选文档的 textBody 域作了三个替换。第三个替换是为了避免句子结尾连续出现两个句号。FIELD textBody := @ReplaceSubstring(textBody; "Acme" : "mousetrap" : ".." ; "Acme, Inc." : "mouse detention device" : ".");SELECT @All8 (@Word)。该样例从字符串 s 中提取单词 n ,用空格作为单词分隔符。s := @Prompt([OKCANCELEDIT]; "String"; "Enter a string of words"; "");n := @Prompt([OKCANCELEDIT]; "Word"; "Enter the number of the word to extract"; "");ss := @Word(s; " "; @TextToNumber(n)); @Prompt([OK]; "Substring"; "Word " + n + " is "" + ss + """)修剪、重复、添加新行,并改变大小写以下函数修剪字符串、重复字符、添加新行(回车),并改变大小写:函数 描述@Trim(string) 从字符串中删除开头、结尾和多余的空格。@Trim(list) 从字符串列表的每个元素中删除开头、结尾和多余的空格,并从列表中删除空白元素。@Repeat(string, number) 将字符串 string 重复 number 次。@NewLine 在字符串中插入一个新行(回车)。@LowerCase(string) 将字符串中所有的大写字符转换成小写。@UpperCase(string) 将字符串中所有的小写字符转换成大写。@ProperCase 将字符串中每个单词的第一个字符转换成大写,并将其余的字符转换为小写。样例:修剪、重复、添加新行,并改变大小写1 (@Trim)。该样例返回 [Now is the time],删除全部多余的空格。Untrimmed := " Now is the time ";Trimmed := @Trim(Untrimmed);@Prompt([OK]; "Untrimmed"; "[" + Untrimmed + "]");@Prompt([OK]; "Trimmed"; "[" + Trimmed + "]")2 (@Trim, @ProperCase)。该样例将 Name 中的单词转换成首字母大写的单词,并删除开头、结尾和多余的空格。如果为 Name 输入“ jane j smith”,该公式将它转换为“Jane J Smith”。@Trim(@ProperCase(Name))3 (@Repeat)。如果 Sales 域大于或等于 100,000,则本样例在 Comments 域中返回“Great Month! Great Month! Great Month!”FIELD Comments := @If(Sales >= 100000; @Repeat("Great Month! "; 3); Sales >= 50000; "Good Month"; "I want to see you in my office");SELECT @All4 (@NewLine)。本样例返回由一个新行分隔开的用户名和日期。@UserName + @NewLine + @Text(@Now)5 (@LowerCase)。本样例返回小写的用户名。@LowerCase(@UserName)6 (@UpperCase)。本样例返回大写的用户名。@UpperCase(@UserName)执行算术运算乘、除、加、减运算符 (* / + -)。乘法和除法运算符的优先级高于加法和减法;运算的顺序是从左到右。以下列出的是一些算术运算函数。函数 描述@Abs(number) 计算一个数的绝对值。@Sign (number) 对于正数返回 1,对于负数则返回 -1,对于零则返回 0。@Sum(num; num; ...) 计算数字和数字列表的和。@Integer(number) 去掉数字的小数部分。@Integer(numberlist) 去掉数字列表元素的小数部分。@Round(number) 对一个数字进行四舍五入。@Round(number; factor) 以一个指定的因子规整数字。@Round(numberlist) 将列表中的每个数字都进行四舍五入。@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) 计算常用对数。@Ln(number) 计算自然对数。@Exp(number) 计算以 e 为底的指数。@Random 返回一个 0 到 1 之间的随机数。@Sin(angle) 计算角度的正弦(弧度)。@Cos(angle) 计算角度的余弦(弧度)。@Tan(angle) 计算角度的正切(弧度)@Asin(sine) 反正弦函数。@Acos(cosine) 反余弦函数@Atan(tangent) 反正切函数@Atan2(x; y) y/x 正切值的反正切函数样例:执行算术运算1 (*, 优先级)。对于第一个 @Prompt,该例样打印 15,因为先执行乘法 4 * 3。对于第二个 @Prompt 该样例打印 21,因为括号强制先运算 3 + 4。@Prompt([OK]; "3 + 4 * 3"; @Text(3 + 4 * 3)); @Prompt([OK]; "(3 + 4) * 3"; @Text((3 + 4) * 3))2 (/ *)。该样例对于第一个 @Prompt 打印 0.333333333333333,先执行除法再将结果四舍五入精确到小数点后 15 位。对于第二个 @Prompt,该样例打印 1.2635268885E+17,显示结果的 11 位小数作为乘以 1017 的系数。@Prompt([OK]; "1 / 3"; @Text(1 / 3)); @Prompt([OK]; "123456789 * 1023456789"; @Text(123456789 * 1023456789))3 (@Abs)。该样例计算 Score1 与 Score2 之差的绝对值。@Abs(Score1 - Score2)4 (@Abs)。该样例计算 Sales 和 CostOfSales 之差的绝对值,在文本域中将它格式化,并用括号括在负数结果两边。GP := @Abs(Sales - CostOfSales); @If(Sales >= CostOfSales; @Text(GP); "(" + @Text(GP) + ")")5 (@Sign)。该代理样例显示 Total 域。如果域值是负数,则在括号中放置的是它的绝对值;如果域值是0,则显示“Zero”。sign := @Sign(Total);display := @If(sign = 1; @Text(Total); sign = -1; "(" + @Text(@Abs(Total)) + ")"; "Zero");@Prompt([OK]; "Total"; display);SELECT @All6 (@Sum)。该样例打印 15,列表 One23、变量 Four 和常量 5 的总和。One23 := 1 : 2 : 3; Four := 4;S := @Sum(One23; Four; 5); @Prompt([OK]; "Sum of 1-5"; @Text(S))7 (@Integer)。该样例将 3.12 截为 3 而将 6.735 截为 6。@Prompt([OK]; "@Integer(3.12)"; @Text(@Integer(3.12))); @Prompt([OK]; "@Integer(6.735)"; @Text(@Integer(6.735)))8 (@Integer)。该样例在列表中将 Sales 和 Commission 截为整数。@Integer(Sales : Commission)9 (@Round)。该样例将 3.12 四舍五入为 3,将 6.735 取为 7,7.5 取为 8;753 以 10 为因子归整为 750; 列表元素中的 3.12、 6.735 和 7.5 分别归整为 3、 6 和 7(把它们转换为文本字符串以显示)。@Prompt([OK]; "@Round(3.12)"; @Text(@Round(3.12))); @Prompt([OK]; "@Round(6.735)"; @Text(@Round(6.735))); @Prompt([OK]; "@Round(7.5)"; @Text(@Round(7.5))); @Prompt([OK]; "@Round(753; 10)"; @Text(@Round(753; 10))); @Prompt([OK]; "@Round(3.12 : 6.735 : 7.5)"; @Implode(@Text(@Round(3.12 : 6.735 : 7.5))))10 (@Max)。该样例打印 3,即 1 和 3 中的最大值,第二个样例打印 99 6 7 8,即分别是两个列表中平行元素的最大值。@Prompt([OK]; "@Max(1; 3)"; @Text(@Max(1; 3))); @Prompt([OK]; "@Max(99 : 2 : 3; 5 : 6 : 7 : 8)"; @Implode(@Text(@Max(99 : 2 : 3; 5 : 6 : 7 : 8))))11 (@Min)。该样例打印 1,即 1 和 3 中的最小值,第二个样例打印 5 2 3 3,即分别是两个列表中平行元素的最小值。@Prompt([OK]; "@Min(1; 3)"; @Text(@Min(1; 3))); @Prompt([OK]; "@Min(99 : 2 : 3; 5 : 6 : 7 : 8)"; @Implode(@Text(@Min(99 : 2 : 3; 5 : 6 : 7 : 8))))12 (@Modulo)。该样例打印 4/3 的余数 1;-14/3 的余数 -2,(当被除数是负数时,余数是负数);还有 1 2 3 3,这分别是第一个列表中的被除数除以对应的第二个列表中的除数的余数。@Prompt([OK]; "@Modulo(4; 3)"; @Text(@Modulo(4; 3))); @Prompt([OK]; "@Modulo(-14; 3)"; @Text(@Modulo(-14; 3))); @Prompt([OK]; "@Modulo(4 : 6 : 8 : 9; 3 : 4 : 5 : 6)"; @Implode(@Text(@Modulo(4 : 6 : 8 : 9; 3 : 4 : 5 : 6))))13 (@Modulo)。该样例确定输入的数字是奇数还是偶数(被 2 除其余数为 0)。n := @TextToNumber(@Prompt([OKCANCELEDIT]; "Input Number"; "Type a number"; "")); @Prompt([OK]; "The number is ..."; @If(@Modulo(n; 2) = 0; "Even"; "Odd"))14 (@Power)。该样例打印 2 的 3 次幂即 8,和 -2 的 3 次幂即 -8,和 2 的 -3次幂即 0.125。@Prompt([OK]; "@Power(2; 3)"; @Text(@Power(2; 3))); @Prompt([OK]; "@Power(-2; 3)"; @Text(@Power(-2; 3))); @Prompt([OK]; "@Power(2; -3)"; @Text(@Power(2; -3)))15 (@Sqrt, @Power)。该样例是用于一个计算域的值公式,计算矩形的斜边,该矩形的边在 Length 和 Width 域中指定。@If(Length = "" | Width = ""; ""; @Sqrt(@Power(Length; 2) + @Power(Width; 2)))16 (@Pi, @Power)。该样例是用于一个计算域的值公式,计算一个圆形的面积,该圆形的半径在 Radius 域中指定。@If(Radius = ""; ""; @Pi * @Power(Radius; 2))17 (@Log)。该样例打印 0.602059991327962 和 14,它们分别是 4 和 1014 的常用对数。@Prompt([OK]; "Common log of 4"; @Text(@Log(4))); @Prompt([OK]; "Common log of 1.0E+14"; @Text(@Log(1.0E+14)))18 (@Ln)。该样例打印 0.693147180559945,即 2 的自然对数。@Prompt([OK]; "Natural log of 2"; @Text(@Ln(2)))19 (@Exp)。该样例计算出第一个 @Exp 函数值为 2.71828182845904 (e 的值),第二个 @Exp 函数值为 3.49034295746184 (e 的 1.25 次幂),第三个 @Exp 函数值为 0.28650479686019 (e 的 -1.25 次幂)。@Prompt([OK]; "e to 1"; @Text(@Exp(1))); @Prompt([OK]; "e to 1.25"; @Text(@Exp(1.25))); @Prompt([OK]; "e to -1.25"; @Text(@Exp(-1.25)))20 (@Random)。
该视图操作样例从用户那里取得一个数字,并将它和 1 到 99 之间的随机数比较,包含 1 和 99 。userNumber := @Prompt([OKCANCELEDIT]; "Number"; "Must be 1-99"; "");winningNumber := @Text(@Integer(98 * @Random + 1));@Prompt([OK]; "Result"; @If(userNumber = winningNumber; "YOU WIN"; "Sorry - winning number is " + winningNumber))21 (@Sin, @Cos)。该样例显示两个计算域的公式。第一个公式计算矩形的长,第二个公式计算矩形的宽。Diagonal * @Sin(Angle * @Pi / 180)Diagonal * @Cos(Angle * @Pi / 180)执行时间/日期运算时间-日期值包含年、月、日、小时、分钟和秒。您可以象在时间-日期域中那样使用一个时间-日期值,但是必须用 @Text 把它们转换为字符串。您可以用 @TextToTime 将一个字符串转换成时间-日期值。时间-日期常量可以是日期、时间或者两者的结合,它们被括在方括号中。日期的格式是年、月、日(年份可选。缺省为当前年;两位数的年如果年数大于或等于 50 表示是在 20 世纪,如果年数小于 50 表示是在 21 世纪),由斜杠 (/) 或连字符 (-)(对于 OS/2)分开。时间的格式是小时、分钟、秒(可选。缺省为 0),由冒号分开。您可以使用 24 小时的时间形式或用“PM”表示下午时间。您可以增加时区以指示一个时区。将这些部分用空格分开。以下是几个时间/日期常量的例子:[6/30/97]、[5:30:00 PM]、[17:30:00]、[17:30 EST]、[6/30 5:30 PM]。日期可以进行比较或做减法。减法操作的结果是以秒为单位的数字。以下的函数用来确定并操作时间-日期值。函数 描述@Created 返回文档创建的时间-日期。@Accessed 返回最后一次访问文档的时间-日期。@Modified 返回最后一次编辑并保存文档的时间-日期。@Now 返回当前的时间-日期。@Today 返回当天的日期。@Tomorrow 返回明天的日期。@Yesterday 返回昨天的日期。@Weekday(time-date) 返回星期几如 1-7(星期日到星期六)。@Day(time-date) 从时间-日期中提取月历中的日期。@Month(time-date) 从时间-日期中提取月份值,从 1 到12。@Year(time-date) 从时间-日期中提取年值。@Hour(time-date) 从时间-日期中提取小时值。@Minute(time-date) 从时间-日期中提取分钟值。@Second(time-date) 从时间-日期中提取秒值。@Date(y; m; d) 返回年,月,日中的日期部分。@Date(y; m; d; h; m; s) 返回年,月,日,小时,分钟,秒中的日期部分。@Date(time-date) 返回时间-日期中的日期部分。@Time(y; m; d) 返回年,月,日中的时间部分。@Time(y; m; d; h; m; s) 返回年,月,日,小时,分钟,秒中的时间部分。@Time(time-date) 返回时间-日期的时间部分。@Adjust(time-date; y; m; d; h; m; s) 通过加减某一数值以调整时间-日期。@Zone 返回当前计算机的时区设置。@Zone 返回当前时间-日期的时区设置。样例:执行时间-日期运算1 (@Created)。如果当前文档是在 1995 年以前创建,则该样例将“Archive”写入 Status 域,否则写入“Current”。SELECT @All;FIELD Status := @If(@Created < [01/01/95 12:00:00 AM]; "Archive"; "Current");2 (@Modified, @Date, @Today, @Yesterday)。根据当前文档最后一次修改的日期,该代理样例将“Today”,“Yesterday”,或“Old”写入到 ViewStatus 域中。FIELD ViewStatus := @If(@Date(@Modified) = @Today; "Today"; @Date(@Modified) = @Yesterday; "Yesterday"; "Old");SELECT @All3 (@Modified, @Date, @Weekday, @Today, @Adjust, @Yesterday)。该样例在上例的基础上做些修改,在指定“Yesterday”时要跳过星期六。如果今天是星期一,则 y 被设置为今天的日期再减去 3 天;否则设置为昨天的日期。用 y 代替 @Yesterday 用于测试 @Modified date。d := @Date(@Modified);y := @If(@Weekday(@Today) = 2; @Adjust(@Today; 0; 0; -3; 0; 0; 0); @Yesterday);FIELD ViewStatus := @If(d = @Today; "Today"; d = y; "Yesterday"; "Old");SELECT @All4 (@Now, @Month, @Year, @Day)。该计算文本域的样例在 letterhead 表单的开头显示当天的日期。例如,6/30/95 显示为“June 30, 1995”。months := "January" : "February" : "March" : "April" : "May" : "June" : "July" : "August" : "September" : "October" : "November" : "December";month := @Subset(@Subset(months; @Month(@Now)); -1);year := @Year(@Now);day := @Day (@Now);month + " " + @Text(day) + ", " + @Text(year)5 (@Adjust, @Weekday, @Created)。这个计算时间域的样例显示距文档创建日期两天的日期。如果创建日期是星期五,则该样例增加 4 天以跳过周末。increment := @If(@Weekday(@Created) = 6; 4; 2);@Date(@Adjust(@Created; 0; 0; increment; 0; 0; 0))访问用户环境用户环境是包含公式的数据库所在的服务器或工作站,公式包括以下情况: 复制公式、由新邮件到达时或定时触发的代理、选择公式或列公式。否则,用户环境是用户运行公式的 Notes 工作站。用户名可以是专有名称也可以是非专有名称,专有名称可以是规范或缩写的,使用 @Name 以改变用户名的格式。以下是在用户环境中返回或处理信息的函数。函数 描述@UserName 返回用户名或服务器名.@Name([key]; name) 改变用户名的结构。关键字包含 [CN] 以从一个专有名字中解析出公共名,[Abbreviate] 缩写规范格式的专有名字,[Canonicalize] 与上述作用相反,[ToKeyword] 将名字各部分按相反顺序排序,用反斜杠分开(用于分类视图)。@UserRoles 对于服务器上的数据库,返回当前用户的角色列表。@MailDbName 返回用户邮件数据库的服务器名和路径名。该函数判定两个元素的列表。@OptimizeMailAddress(address) 从地址中删除无用的网络域。@Platform 返回用户当前运行的平台:Macintosh、NetWare、OS2V1、OS2V2、UNIX、Windows/16 或 Windows/32。@Version 返回正在运行的 Notes 版本(字符串)。@Password(string) 对一个字符串编码。您无法从编码结果中确定原始字符串。样例:访问用户环境1. 该视图选择公式限制视图中只能有这样一些文档,这些文档的 From_1 域与当前用户名相匹配。From_1 和 @UserName 都被缩简至层次名称的公共名部分,以更好地确保匹配。SELECT @Name([CN]; @UserName) = @Name([CN]; From_1)2. 该列公式中,@Name 函数从“From”域中提取公共名部分。Subject + " (" + @Name([CN]; From) + @DocDescendants(")"; ", % response)"; ", % responses)")3. 该公式显示有关用户环境的信息。@MailDbName 的返回值被组合,因为这是一个包含服务器名和路径名的二元素列表。@Prompt([OK]; "User name"; @Name([CN]; @UserName));@Prompt([OK]; "Mail database"; @Implode(@MailDbName));@Prompt([OK]; "Platform"; @Platform);@Prompt([OK]; "Notes version"; @Version)4. 这是“By Author”视图中第一列的公式。它转换 From 域,该域包含了一个典型的专有名字,将其转换为姓、逗号、名的格式。AuthorName := @If(!@IsAvailable(From);"Anonymous";@Name([CN]; From));Name := @Trim(@Word(AuthorName; "("; 1));LastName := @RightBack(Name; " ");FirstName := @LeftBack(Name; " ");CombinedName := LastName + ", " + FirstName;@If(CombinedName = ", "; Name; CombinedName)5. 这是一个对于某表单的“Password”域的输入校验公式。作者可在口令被键入的时候看到口令,但是文档被保存后,口令被编码而且不可读。@Password(Password)访问当前数据库和视图您对运行公式的数据库有直接的访问权(便捷图标公式除外,它没有数据库环境)。如果您在视图的上下文中,则您对运行公式的视图有直接的访问权。在文档的上下文中,您对文档所在视图有直接的访问权。数据库和视图属性下表列出返回数据库和视图属性的函数。函数 描述@DbManager 返回当前对数据库有“管理者”权限的用户、群组和服务器。 返回一个列表。@DbName 返回当前 Notes 服务器和数据库的名字。返回二元素列表。@ViewTitle 返回当前视图的标题。窗口标题和列公式函数许多函数提供了有关视图的答复层次和其他方面的信息。文档在视图中以 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) 与上相同,只是返回 true 代替星号。@IsCategory(true; false) 与上相同,只是如果没有域是分类的话则返回 false 。@IsExpandable 如果当前行是可展开的话则返回一个加号。@IsExpandable(true) 与上相同,只是返回 true 代替加号。@IsExpandable(true; false) 与上相同,只是如果当前行是不可展开的话则返回 false 。样例:访问当前数据库和视图1. 该样例显示数据库标题、它的服务器和数据库名和对它具有“管理者”权限的用户名。@Prompt([OK]; "Title"; @DbTitle);@Prompt([OK]; "Server and database"; @Trim(@Implode(@DbName)));@Prompt([OK]; "Managers"; @Implode(@DbManager; ", "))2. 如果视图标题公式为新文档显示“New Title”。如果视图标题为“AuthorView”则显示 Subject 域;否则显示 Subject 域加上答复文档的数目。StandardTitle := Subject + @DocDescendants(" (No Responses)"; " (1 Response)"; " (% Responses)");@If(@IsNewDoc; "New Topic"; @ViewTitle = "AuthorView"; Subject; StandardTitle)3. 该列公式显示 Subject 域、用户名和答复文档的数目。Subject + " (" + @Name([CN]; From) + @DocDescendants(")"; ", % response)"; ", % responses)")在公式语言中访问当前文档对于表单操作、按钮、热点和域公式,当前文档被是打开的那个。对于视图操作,当前文档是突出显示的那个(不是复选的文档)。对于代理,当前文档是根据代理构造器选择以及 SELECT 关键字标准激活的那个文档。要阅读当前文档中的域,命名该域。命名不区分大小写,但是名字必须准确。要编写当前文档中的域,您必须使用 FIELD 关键字或者 @SetField 函数。不能简单的命名该域。关键字 FIELD 用于赋值语句并有以下格式。如果省略关键字 FIELD,则指定的变量将被认为是临时变量。FIELD field-name := expression@SetField 写入域,它与使用关键字 FIELD 的赋值语句效果相同。@SetField 可以嵌套到另一个语句;对于关键字 FIELD 则不能这样做。域名被表达成一个文本值,所以它可以是一个括号中的准确名字。有一个限制是 @SetField 只工作于已存在的域中;如果文档中不存在您想写入的域,则可使用 FIELD 赋值语句在公式开头“声明”它。@SetField 有以下格式。@SetField( field-expression-name; expression )DEFAULT 关键字是为了处理当一个域不在文档中的情况。如果文档中存在此域,那么就用它的值,如果没有,则用 DEFAULT 值。DEFAULT field-name := expression@MailSend 函数邮寄文档。不带参数的 @MailSend 邮寄当前文档,它必须包含一个域名为“SendTo”,该域包含收件人。带有参数的 @MailSend 构造文档并按指定参数邮寄。@MailSend@MailSend( to; cc; bcc; subject; body; fields; flags )@DeleteField 函数删除域。在 FIELD 赋值语句中指定它作为表达式。FIELD field-name := @DeleteField@DocMark([NoUpdate]) 函数防止文档中公式的改变被保存。在公式处理完以后的文档与以前是一样的。该函数只影响代理。该表格列出返回文档和域属性的函数。函数        描述
@DocumentUniqueID        返回文档唯一的标识符,这是文档的全部复本唯一的标识符;在一个域中,创建一个当前文档的文档链接。
@InheritedDocumentUniqueID        返回文档的父文档唯一的标识符,在一个域中,创建一个当前文档的文档链接。
@NoteID        返回“NT”和跟在后面的文档的项目标识符。
@DocLength        返回文档的字节数大小。
@Author        返回全部作者的缩写姓名。
@DocFields        返回文档中全部域的名称。
@IsAvailable(field)        如果某域存在于文档中,则返回真 (1)。
@IsUnavailable(field)        如果某域不存在于文档中,则返回真 (1)。
@Attachments        返回附件的个数。
@AttachmentNames        返回全部附件的文件名。
@AttachmentLengths        返回每个附件的字节数大小。
@Responses        返回在当前视图中当前文档的答复数目。
@AllChildren        选择匹配文档的直接答复;只用于选择公式中。
@AllDescendants        选择匹配文档的所有答复;只用于选择公式中。
@IsResponseDoc        如果文档是一个答复则返回真 (1)。
@IsNewDoc        如果文档未被保存过则返回真 (1)。
@IsDocBeingEdited        如果文档处于编辑模式则返回真 (1)。
@IsDocBeingLoaded        如果文档正在被加载则返回真 (1)。
@IsDocBeingSaved        如果文档正在被保存则返回真 (1)。
@IsDocBeingMailed        如果文档正在被邮寄则返回真 (1)。
@IsDocBeingRecalculated        如果文档正在被重新计算则返回真 (1)。
样例:访问当前文档1. 该计算域公式执行涉及文档中另外两个域的数学计算。这些域必须存在于文档中,必须是数字型的且被初始化为数字值。TotalSales - CostOfSales2. 该代理样例在当前文档中的两个域上执行数学运算,并将结果指定给第三个域。这两个被引用的域必须存在;GrossSales 域可以是新的。FIELD GrossSales := TotalSales - CostOfSales;SELECT @All3. 该代理公式在当前文档中的两个域上执行数学运算,并将一个数值放到第三个域中或者发送一个邮件信息。第一个语句初始化 GrossSales,如果您能肯定该域已经存在则无须此初始化操作。FIELD GrossSales := 0;gs := TotalSales - CostOfSales;@If(gs > 0; @SetField("GrossSales"; gs); @MailSend("Ian Perron"; ""; ""; "No gross sales"; "Gross sales are zero or less for "; Subject));SELECT @All4. 该列公式样例判定文档是否包含 KeyThought 域。如果文档不包含一个 KeyThought 域,则它的缺省值为 Topic。DEFAULT KeyThought := Topic;KeyThought5. 这是另一种实现上例的方法。@If(@IsAvailable(KeyThought); KeyThought; Topic)6. 该代理样例删除 GrossSales 域。@If (@IsUnavailable(GrossSales); @Return(""); "");FIELD GrossSales := @DeleteField;SELECT @All7. 该代理样例计算 GrossSales 域,然后显示结果,而且不标记文档为更新。作为结果,保存的文档未发生任何变化。如果忽略了 @DocMark 或者指定了“@DocMark([Update])”则变化被保存。FIELD GrossSales := TotalSales - CostOfSales;@Prompt([OK]; "Gross sales for " + Subject; @Text(GrossSales));@DocMark([NoUpdate]);SELECT @All8. 该样例显示当前文档中的全部域。@Prompt([OKCANCELLIST]; "Fields"; "Fields in document"; ""; @DocFields);SELECT @All9. 该窗口标题公式为一个新文档显示“New Document”。对于一个现有的文档,该公式显示 Subject 域和答复的个数。@If(@IsNewDoc; "New Document"; Subject + " with " + @Text(@Responses) + " response(s)")10. 该视图选择公式选择全部文档,除了那些 Form 域包含“Profile”或“Log”的文档。SELECT !@Contains(Form; "Profile" : "Log")11. 该视图选择公式选择所有 Subject 域包含“acme”(不区分大小写)的文档和它们的子文档。SELECT @Contains(@LowerCase(Subject); "acme") | @AllDescendants12. 该表单操作公式显示所有文档附件的名称和长度,或者如果文档没有附件的话,则显示“No attachments”。@If(@Attachments > 0; @Prompt([OKCANCELLIST]; "Attachments"; "Attachment names and lengths"; ""; @AttachmentNames + " (" + @Text(@AttachmentLengths) + " bytes)"); @Prompt([OK]; "Attachments"; "No attachments"))访问在当前文档和数据库以外的数据以下函数从指定的数据库中取得数据值。您不能用这些函数设置数值。@DbLookup 在指定数据库的指定视图中的第一个排序列中查找指定的值。对于每个匹配搜索值的文档,@DbLookup 返回文档或视图中的列的指定域中的值。@DbColumn 返回指定数据库的指定视图中的指定列中的全部值。前三个参数对于每个函数是相同的:[NOTES] : [NOCACHE] 指定操作是在 Notes 数据库上并且未使用缓存。因为 [NOTES] 是缺省的,所以您可以为第一个列表元素指定空串 ""。如果数据是稳定的或者您访问过数据库许多次了,则可以指定第二个列表元素为空串 "",不使用缓存。您可以为整个参数使用一个空串 "" 表示打开一个 Notes 数据库而且没有缓存。server : database 指定您访问的服务器和数据库。为第一个列表元素指定空串 "" 以表示本地 Notes 目录。指定整个参数为空串 "" 以表示当前数据库。您可以指定整个参数为数据库的复本标识符。Notes 将在本地和服务器上搜索,并且使用第一个它找到的复本。从“文件”“数据库”“设计摘要”“复制”中取得复本标识符。view 指定通过哪个视图访问数据库。对于 @DbLookup,第四个参数是关键字,它是在视图中第一个排序列中找到的值。@DbLookup 查找每一个与该关键字匹配的文档。对于 @DbLookup,第五个参数或者是数据库中的域名,或者是视图中的列号。@DbLookup 返回找到文档中的域或列值的列表。对于 @DbColumn,第四个参数是列号。@DbColumn 返回列中全部值的列表。以下函数在当前数据库中取得并设置另一个文档的域值。不过,您必须知道文档的唯一的标识符。@GetDocField(unid; fieldName) 给定唯一标识符,取得域值。@SetDocField(unid; fieldName; value) 给定唯一标识符,设置域值。由于父文档的唯一标识符在子文档的 $Ref 域中,所以该函数适用于在答复文档的父文档中访问并设置域值。在设置了“公式继承选定文档中的数值”的文档中,可以在基文档的一个隐藏域的公式中使用 @InheritedDocumentUniqueID,然后在继承文档的一个隐藏域的公式中使用上述隐藏域的名称。否则,您必须设计一种保存并提取您想访问文档的标识符的方法。样例:访问当前文档和数据库以外的数据1. 该样例在本地通讯录 (NAMES.NSF) 的“个人”视图中查找一个人的姓名,并从包含此姓名的文档中取得办公室的电话号码。inputName := @Prompt([OKCANCELEDIT]; "User name"; "Enter user name as FIRST LAST"; "");adjName := @Right(inputName; " ") + ", " + @Left(inputName; " ");phoneNumber := @DbLookup("Notes" : "NoCache"; "" : "NAMES"; "People"; adjName; "OfficePhoneNumber");@Prompt([OK]; "Office phone number"; inputName + "'s office phone is " + phoneNumber)2. 该样例除了有两点不同以外,与上面的例子一样。在这里使用的是通讯录数据库的复本标识符而不是其名称。Notes 先在本地搜索复本然后上服务器搜索,并确定使用第一个发现的复本。@DbLookup 的最后一个参数是列 2,而不是 OfficePhoneNumber。实际上它与列 2 是一回事,因为列 2 中就包含电话号码。inputName := @Prompt([OKCANCELEDIT]; "User name"; "Enter user name as FIRST LAST"; "");adjName := @Right(inputName; " ") + ", " + @Left(inputName; " ");phoneNumber := @DbLookup("Notes" : "NoCache"; "85255AD6:006AE971"; "People"; adjName; 2);@Prompt([OK]; "Office phone number"; inputName + "s office phone is " + phoneNumber)3. 该样例在本地通讯录的“群组”视图中查找一个群组的成员。groupName := @Prompt([OKCANCELEDIT]; "Group name"; "Enter group name"; "");members := @DbLookup("Notes" : "NoCache"; "" : "NAMES"; "Groups"; groupName; "Members");@Prompt([OKCANCELLIST]; "Group members"; "Members of " + groupName; ""; members)4. 该样例在给出办公室电话号码的情况下,取得与该号码有关的人员的姓名。设计这个例子是为了在号码与人员一一对应的情况下使用的。由于数据库没有按电话号码排序的视图,所以此样例使用 @DbColumn 来获得所有电话号码(列 2)和所有人员(列 1),然后查找与号码对应的人员。phone := @Prompt([OKCANCELEDIT]; "Phone number"; "Enter phone number"; "");phoneList := @DbColumn("Notes" : "NoCache"; "" : "NAMES"; "People"; 2);nameList := @DbColumn("Notes" : "NoCache"; "" : "NAMES"; "People"; 1);position := @Member(phone; phoneList);@If(position = 0; @Do(@Prompt([OK]; "Not listed"; "No listing for " + phone); @Return(" " )); "");name := @Subset(@Subset(nameList; position); -1);nameAdj := @Right(name; " ") + " " + @Left(name; ",");@Prompt([OK]; "Phone number " + phone; nameAdj)使用函数通过 ODBC 访问外部数据库以下函数通过 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”作为一个关键字在需要用户标识符和口令的地方,您可以指定空串并让用户在执行函数时提供它们。样例:使用函数通过 ODBC 访问外部数据库1. 该公式取得 MANUALS 表中的 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")
版权所有2001
首页-->LOTUS文章-->强迫WEB用户重新登录的方法(两种)
我们知道在Web应用中,一旦我们曾经输入过一次用户口令,那么,系统将会一直使用当前Web用户与Domino HTTP服务器连接。如果我们希望以另一名Web用户访问此服务器,只好关闭浏览器,再重新连接服务器时,重新输入其他用户的名程和口令。(这是我们不愿意作的。)如何不关闭浏览器,强迫浏览器显示登陆窗口呢?关于如何强迫一个Web用户输入登录口令的方法,在此列出两个:1。Java Servlet程序2. Domino URL 命令这两种方法都是强迫浏览器显示登陆窗口,以使用户可以其他用户名登陆。实际上不能注销当前已连接Web用户,除非在登陆窗口输入错误的用户名和口令或者不同于当前已连接的Web用户的正确的用户名和口令。注释:这段程序有一段逻辑错误,本人已经对其更正这段程序可以使用Domino URL命令代替,功能完全相同。在R5中试验可行,4.6估计可行。比如:http://host/anydatabase.nsf?login注意这里一定要写一个数据库的名字,可以是任何一个数据库,甚至是不存在的数据库。请注意格式。你可以试试,有结果您可以写信给我。-------丁香书 1999.8.13=====================================问题5:怎么使一个Web用户注销登录?来自:http://dingxiang.163.net更新:1999.8.14解答:How do you add a logoff button for web users?Using the username:[email protected] URL does not work because the browser thinks your realm is "username:[email protected]" instead of "www.company.com".

You can use a Java servlet to pass a 401 exception to the browser to log someone off of TestRealm (in most cases, this is "www.company.com"):

// From Terry Courtneyimport java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class LogOff extends HttpServlet {  public void init(ServletConfig config)  throws ServletException  {    super.init(config) ;  }  public void doGet (HttpServletRequest req, HttpServletResponse res)  throws ServletException, IOException  {    String auth ;    // get output streem    ServletOutputStream out = res.getOutputStream() ;    // get authorization from header    auth = req.getHeader("Authorization") ;//    if (auth == null) { //It's a logic Error,rem by dingxiang    if (auth != null) {      // force prompt of login      res.setContentType("text/html");      res.setHeader("WWW-Authenticate", "WWW-Authenticate: basic realm=\"/TestRealm\"") ;      res.setStatus(401, "401 Unauthorized") ;    }    out.println("Hi") ;  } // end doGet} // end class LogOffWhen you call this servlet w/ a URL of http://www.company.com/LogOff, it will send the right HTTP headers back to the browser to trick the browser into thinking that the user did not authenticate with this Realm.

This servlet can also be written as a LotusScript agent.  
版权所有2001
首页-->LOTUS文章-->自动切换用户ID
  Sub Initialize%REM 切换用户ID本程序的作用是:1、从指定的目录中搜索所有ID文件,2、然后提供用户选择3、再从注册用户时保存在names.nsf数据库中的密码进行校对看是否相同。4、再通过用户选择的ID文件更换操作员ID本程序有以下缺陷:1、取得特殊指定目录下的ID文件2、无法判断获得的ID文件是用户ID,还是Server.id,cart.id3、用户密码需要保存在names.nsf中,首先进行密码核对,造成安全泄露。但不这样做,无法截获用户输入密码错误,程序不好处理。4、因此此公式仅适合单机用户。tndahttp://notes.363.net%END REMREM 首先搜索指定目录下的所有ID文件Dim ss As New notessessionxx = ss.UserNameListIDPath = ss.GetEnvironmentString("Directory",True)IDFileAll = IDPath+"\*.id"'没有方法遍历所有目录,所以假定所有ID文件在此目录下fileName = Dir(IDFileAll, 0)num = 0Do While fileName <> ""num = num + 1fileName = Dir()LoopIf num = 0 Then Exit Sub '没有找到ID文件就退出程序Redim ids(1 To num) num = 1fileName = Dir(IDFileAll, 0)Do While fileName <> ""ids(num) = Left(filename,Instr(1,filename,".")-1)num = num + 1fileName = Dir()LoopDim uiw As New notesuiworkspaceIDuser = uiw.Prompt( PROMPT_OKCANCELLIST,"更换操作员","请选择一个操作员:","",ids)If IDuser = "" Then Exit SubREM 从names.nsf中获取密码 Dim ndb As notesdatabaseSet ndb = ss.getdatabase("","names.nsf")Dim nvi As notesviewSet nvi = ndb.getview("People")Dim pdoc As notesdocumentSet pdoc = nvi.getdocumentbykey(IDuser)pass = pdoc.comment(0)For i = 1 To 3IDpass = uiw.Prompt( PROMPT_PASSWORD,"输入密码","请输入您的密码:")If pass = IDpass Then Exit For NextIf pass <> IDpass Then Exit SubDim idfile As Stringidfile = IDPath+"\"+IDuser+".id"Dim newreg As New NotesRegistration Call newreg.SwitchToID( idfile,pass )Messagebox "当前操作员是:"+IDuser,64,"更换成功"Exit SubEnd Sub  
版权所有2001
首页-->LOTUS文章-->RTF域全接触
   RTF域完全掌握 1、RTF域是Notes中最重要的域,由于它能够保存文本、声音、图片、动画、附件以及按钮操作、链接等多媒体信息资料,是Notes不同于普通关系型数据库的重要特征。2、RTF域的存储容量仅受机器硬盘限制,可以任意大的文件和程序。3、当存储各种类型的RTF域,改变域类型成为文本类型时,仅其中的文本字符能够保存,其他的图片、附件等不能保存在文本类型域中。4、RTF域在编程中,是不能通过公式函数来计算处理的,只能通过LotusScript来对其进行复制操作。5、以下是用LotusScript复制、添加RTF域内容,经测试无误。Sub Click(Source As Button)%REM RTF域复制操作作者:tnda 2000.3.5http://notes.363.net mail:[email protected]域值在程序中编辑修改后,只能调用doc.save(false,true)不能调用uidoc.save,也不能在文档中单击保存按钮,否则后台修改无效。并且其修改的内容当前不能显示,只有不保存退出文档,再打开才能显示。因为RTF域只能通过后台LotusScript修改。%END REM Dim uiw As New notesuiworkspaceDim uidoc As notesuidocumentDim doc As notesdocumentSet uidoc = uiw.currentdocumentSet doc = uidoc.document '获得当前文档Dim rtitem1 As NotesRichTextItemSet rtitem1 = doc.GetFirstItem( "aaaa" )If rtitem1 Is Nothing Then Exit SubIf rtitem1.Type <> RICHTEXT Then Exit Sub'文档没有保存以前,无法发现RTF域Call rtitem1.AppendText( "这是第一个域值内容" ) Dim rtitem2 As NotesRichTextItemSet rtitem2 = rtitem1.CopyItemToDocument( doc,"bbbb")Call rtitem2.AppendText( "这是第二个域新内容" ) Call doc.Save( False, True )End Sub
版权所有2001
首页-->LOTUS文章-->域公式
  域公式本节主要介绍域在何处需要使用公式,具体公式编程,下章介绍。可以使用域公式的事件有:域选择公式、域隐含公式、域初始值和计算公式、域校验公式、域刷新公式、域事件公式等。域选择公式:在选择域类型为对话框列表、复选框、单选框、列表框、组合框时,需要输入域可以选择的选项。选择项:对于固定不变的选择项,如:性别、学历等,可以手动输入。对于可能会变化,或者说无法确定的选择项目,如:毕业学校、所学专业等,需要使用公式获得。手动输入的方法为:在选择项目输入框内,输入男|1女|2“|”前面的“男”,为显示内容,即用户可以看见的,“|”后面的“1”,为实际内容,即当用户选择了“男”时,实际上此域保存的内容是“1”。因此,此域在表单中显示为“男”,但在视图中显示为实际值“1”,如果希望显示和保存的内容一致,则放弃使用“|”,即可,如:男女注意:“1”是文本字符,不是数字类型,利用选择项输入的域类型一律是文本类型。一行为一个选择项目,如果选择项内容需要空值,直接回车,在一行中不要输入任何内容即可。公式选择项的方法为:1、从视图读取:需要使用公式:@DbColumn,此公式的作用是从任何服务器上的数据库中,读取指定的视图中的指定的列。@DbColumn( class : "NoCache" ; server : database ; view ; columnNumber )·        class:输入空值,或"Notes" ·        "NoCache":不从缓冲区读取 ·        server:指定的服务器,本地服务器,可以使用空值"" ·        database:指定的数据库名称,如果找不到则报错,如果是当前数据库则为空值 ·        view:指定的视图,可以为隐含视图, ·        columnNumber:指定视图中的第n列,可以是隐含列,从视图的左数第N个列,包括隐含列。 ·        注意:如果视图的第N列有重复内容,则选择项目中也会有重复内容供选择,需要第N列排序分类即可。 如:@DbColumn("";"";"aaaa";1),读取当前数据库的aaaa视图的第1列值。2、从其他文档读取:可以从其他文档读取一个多值域,作为选择项目,如可以将公司的部门输入指定配置属性文档中,供选择。使用公式@DbLookup,此公式的作为是从指定的文档中读取一个域值内容。@DbLookup( class : "NoCache" ; server : database ; view ; key ; fieldName )·        key:根据此值,从视图中的第一列选择指定的文档,如果key是多值列表,如:"男":"团员",则视图第一列性别必须排序分类,第二列政治面貌必须排序,依此类推。 ·        filedName:指定文档中的域名称,函数将返回此域的值。如果输入数字N,则表示读取视图中指定文档的第N列值,建议使用域名称。 ·        注意:如果没有找到文档,系统将定位为此视图的第一个文档,如果视图中没有文档,则出错。 如:@DbLookup("";"" ;"aaaa"; "abc" ; "City" ),返回从当前数据库中aaaa视图,根据aaa值定位的文档的City域值。返回内容必须是多值域,如果是单值域,则仅有一个选项。使用视图对话框的方法:选择项目可以使用视图来选择,可以使用函数:@PickList,其作用是显示一个视图对话框,可以从中选择一个或几个文档,并返回指定的域值。@PickList( [Custom] : [Single] ; server : file ; view ; title ; prompt ; column ; categoryname )·        [Custom]用户自定义选项 ·        [Single]:选择此项表示返回单值,否则可以返回多值 ·        title:对话框标题 ·        prompt:对话框显示提示 ·        column:返回第N列域值 ·        categoryname:5.0新增功能,表示视图仅显示此参数指定的分类文档。 ·        此函数功能比较多,具体请参阅帮助数据库 使用地址对话框的方法:显示names.nsf的人员视图供选择使用存取权限框的方法:显示当前数据库存取权限指定的人员供选择如果选择了域属性“可以使用不在列表中的数值”,则可以从选择项目中选择的同时,可以输入新建内容。域隐含公式:域隐含公式必须返回@True或@False,来根据逻辑对或错判断域是否隐含。如:aaa = "",表示域aaa的值为空时隐含域初始值和计算公式:根据公式返回一个指定的值,返回的值类型必须和域类型一致,否则出错。公式位置为:Default Value如:文档创建时间域初始公式可以为,@New,表示返回当前工作站时间域校验公式:判断域值输入是否正确,仅编辑类型可以使用。公式位置为:Input Validation如:数量域值大于0,小于100,其公式为@If(ShuLian > 0 & ShuLiang < 100 ; @Success ; @Failure("数量输入范围是0 ~100"))·        @Success:返回正确,表示此域内容符合要求。 ·        @Failure:返回错误,表示此域内容不符合要求,并显示提示内容,且输入光标会自动进入此域输入框中。。 域刷新公式:当表单刷新时,会根据此公式返回的值改变域值内容,仅编辑类型可以使用。公式位置为:Input Translation如:如果在省份输入的内容是“广东”则自动将其内容转化为“广东省”@If(@Right(ShengFen;1) = "省" ; ShengFen ; ShengFen + "省" )此公式判断ShengFan域内容,最后一个字符是否是“省”,如果不是,则加入,否则返回原值。域事件公式:根据域触发的事件运行一段程序,事件有:·        Entering:进入编辑域,即此域获得输入焦点,激活此事件,一般可以设置动态初始值。 ·        Exiting:退出编辑域,即此域失去输入焦点,激活此事件,一般可以校验域,或根据域内容改变其他域内容。 ·        Initialize:域调入内存时,激活此事件 ·        Terminite:域从内存中退出时,激活此事件  
版权所有2001
首页-->LOTUS文章-->代理调试方法
     在notes编程中,尤其web开发中,代理是很常用的,但是,相对而言,代理的调试也很不方便。下面就我的经验,列出几种常用的方法,欢迎大家指正和补充。(这里讨论的都是lotussript代理)  1.notes端跟踪法  使用notes开发客户端时,对于那些在客户端运行的代理(与服务器端运行相对)可以直接跟踪代理的运行。  方法:选中菜单中“文件-〉工具-〉调试lotusscript”后再运行代理即可。  优点:调试直观,代理可以单步跟踪、设置断点(使用stop语句或者在调试窗口中双击需要加断点的语句)、观察该中断时刻的各种变量和对象的值等。所以使用这种调试方法可以很快地知道问题的所在。  缺点:只适用于客户端运行的代理,而且有些web客户端运行的代理也无法调试。 一些调试技巧:  可以把那些在服务器端运行的代理的核心代码先在客户端调试通过后,再改回服务器端运行,这样有时可以节省很多调试时间。有些web端运行的代理也可照此方法调试。  2.输出调试信息  对于那些在服务器端运行的代理,或在web端运行的代理,可以利用print命令来输出一些关键的信息来帮助你了解问题的所在。  方法:在代理中怀疑有问题的地方或分支点附近加入print命令来打印一些关键信息,从而帮助了解问题的所在。  a.对于处于非调试状态下的notes客户端而言,print的信息将显示在客户端下部的状态条中。  b.对于处于调试状态下的notes客户端而言,print的信息将显示在客户端下部的状态条中和调试窗口的输出窗口中。  c.对于web应用而言,print的信息将直接输出到浏览器中。(但请注意,位于表单的webqueryopen代理中的print输出都将被忽略)  d.对于服务器端运行的代理,print的信息将保存在NOTES.LOG文件中  附带说一句,当代理产生错误时,一般都会在domino的命令窗口中产生一个错误信息,说明错误的类型,可能的话,先看看这些信息,了解一下错误的类型,可能会比一上来就盲目地跟踪要好一些。当然,在程序编码的同时,利用on error来建立一些错误报告机制,也有助于更快地分析错误的类型与所在。  3.代理日志  建立一个代理日志,这恐怕是最通用的方法了,但也相对较繁琐。 方法:在代码的头部加入如下代码(xxxx为一随意的日志名,mylog为日志对象的名称,可以自定):dim mylog as new noteslog("xxxx")call mylog.openagentlog在代码需要调试的部分加入下面的语句call mylog.logaction(description$)description$ 是一个字符串常量或变量,内容根据调试的需要而定。这些内容将会写到代理的日志中。  日志的察看方法:  a.在designer中打开数据库的代理窗口。  b.在制定的代理上面点击鼠标的右键  c.在弹出的菜单中选择日志即可  注意,代理一旦重新保存,代理日志就会被清空。代理每次运行,新产生的代理日志会覆盖上一次的日志。
版权所有2001
      某食堂管理系统,希望根据不同的用户身份,进入相应的页面。下面就是实现这种功能的一段代理的Initialize代码: Sub InitializeOn Error Goto errorsDim session As New notessessionDim note As notesdocumentDim result As VariantDim i As IntegerDim viewname As StringDim querystring As StringDim doc As notesdocumentDim view As notesviewDim normaluserday As VariantDim useridday As Variantnormaluserday=Evaluate("@If(@Weekday(@Today)=1;@Adjust(@Today;0;0;-6;0;0;0);@Adjust(@Today;0;0;2-@Weekday(@Today);0;0;0))")Const notesmcro$=|@ismember("[manager]";@userroles)|'format后的normaluserday是字符串型useridday=Format(normaluserday(0),"yyyy-mm-dd")Set db=session.currentdatabaseSet view=db.getview("vshipu")Set doc=view.getdocumentbykey(useridday)Set note=session.documentcontextresult=Evaluate(notesmcro$)If result(0)=1 Then 'administratorPrint "[/"+getdbpath+"/myview?openform&view=vshipu]"Else 'normal userIf (doc Is Nothing) ThenPrint "本周食谱还未制订,请下次再来,谢谢合作."End IfPrint "[/"+getdbpath+"/vshipu/"+doc.universalid+"?opendocument]"End IfExit Suberrors:Print ErrExit SubEnd Sub
版权所有2001
Notes R5同外部邮件系统路由的设置
 
我们知道NOTES作为内部邮件系统是非常简单易行的,很受大家欢迎,但习惯于使用
OUTLOOK的用户还是喜欢使用POP3邮件格式,尤其是要和外界的POP3用户交流信息,其实
,NOTES不仅提供了POP3服务,而且还能通过SMTP(简单邮件传输协议)和其他的POP3邮
件系统进行通讯。本文主要介绍如何和其他POP3邮件系统交换信息。
  目标:
  1、使NOTES能够同外部邮件系统通讯(包括接收和发送外来和外出邮件);
  2、为组织内的非应用用户提供NOTES的邮件服务,这些用户无须安装NOTES客户端软
件,就可以使用NOTES的邮件系统。
  配置服务器需要:
  1、为服务器 启用"在本地 Internet 网络域外部发送消息时所用的 SMTP"
  2、为服务器 启用 SMTP侦听 任务
  3、正确设置 DNS 将 DNS服务器 设置为合适的邮件连接服务器。
  具体步骤:
  一、设置到本地 Internet 网络域外部的 SMTP 路由
  必须启用 SMTP 路由在本地 Internet 网络域外部发送消息(如 Internet 或其他
私有网络)。
  启用到本地 Internet 网络域外部的 SMTP 路由:
  1.确保已准备好发送邮件到 Internet 的系统。
  2.确保已经配置了服务器的"配置设置"文档。
  3.在 Domino Administrator 中,单击"配置"附签,然后展开"消息处理"区段。
  4.选择"配置"。
  5.选中"配置设置"文档,然后单击"编辑配置"。
  6.在"路由器/SMTP"下的"基本"附签,完成以下域,并保存文档:
  域 (在本地 Internet 网络域外部发送消息时所用的 SMTP)
  输入 (启用,使用 SMTP 将邮件路由到 Internet)
  二、设置服务器接收通过 SMTP 路由发送的邮件
  要设置服务器接收 SMTP 路由的消息,必须启用"侦听程序"。然后,服务器则可以
"侦听"TCP/IP 端口(通常端口为 25)上的 SMTP 通信量,并可接收 MAIL.BOX 数据库
中的 SMTP 消息。
  注释: 不要将 SMTP 作为任务添加到 Notes .INI 文件中的任务列表中,否则此功
能将无法工作。
  启用"侦听程序":
  1.在 Domino Administrator 中,单击"配置"附签,然后展开"服务器"区段。
  2.选择要编辑的"服务器"文档,然后单击"编辑服务器"。
  3.在"基本"附签,完成以下域,然后保存文档:
  域 (SMTP 侦听 任务)
  输入 ("启用",打开侦听程序,以便服务器可以接收通过 SMTP 路由路由的消息)
  三、网络域命名服务 (DNS) 和邮件路由
  在网络域名服务器上设置Domino邮件服务器的 A记录和MX记录,使得Domino邮件服
务器能够接收外来邮件;在网络域名服务器上设置非Domino邮件服务器的 A记录和MX记
录,使得Domino邮件服务器能够发送外出邮件。
  示例:
  该示例实现了两个Domino邮件服务器的邮件路由。
  1、首先要确认两台服务器能够互相PING通;
  2、要确认两台服务器内部邮件收发畅通;
  3、对Domino服务器进行上述修改;
  4、在网络域名服务器上设置上设置A记录和MX记录。
Domino服务器1的A记录为:
apps.salmon.forward.com. IN A 147.25.8.17
Domino服务器1的MX记录为:
salmon.forward.com. IN MX apps.salmon.forward.com.
Domino服务器2的A记录为:
mail.ntserver.nmg.gov.cn. IN A 147.25.8.66
Domino服务器2的MX记录为:
ntserver.nmg.gov.cn. IN MX mail.ntserver.nmg.gov.cn.
  注:
  1、在邮递失败和 Domino 发送无法邮递消息之前,消息在服务器之间所能传送的最
大次数默认是25次。
  2、服务器在重试将消息传送到另一台服务器之前应等待的分钟数。如果再次失败,
Domino 将增加等待时间。缺省为 15 分钟。
  环境介绍:
  1、邮件服务器
  2、DNS服务器设置
首页-->LOTUS文章-->如何在Notes开发的程序中实现修改留痕
如何在Notes开发的程序中实现修改留痕Notes 的一项很强大的功能是能够实现工作流审批,但是如何能使每个审批人在对原文进行修改后留下他们的修改痕迹呢?本人写了一段程序在Notes中嵌入word 97实现了该功能,希望对网友有所帮助。具体实现如下:首先在表单中创建一个RTF域body,以嵌入word 97,再做一按钮,该按钮实现对修改的留痕。其源程序为:Dim db As New notesdatabase(servername,"jswfw")Dim handle As Variant Dim doc As NotesDocumentSet doc=source.documentDim rtitem As VariantDim object As NotesEmbeddedObject Set rtitem = doc.GetFirstItem( "Body" )Dim session As New NotesSessionDim user As Stringuser = session.commonUserNameIf ( rtitem.Type = RICHTEXT ) ThenSet object = rtitem.GetEmbeddedObject_( "Microsoft Word 文档" )Set handle=object.activate(False)handle.showgrammaticalerrors=Falsehandle.showspellingerrors=Falsehandle.TrackRevisions = Truehandle.PrintRevisions = Truehandle.ShowRevisions = Truehandle.parent.UserName = userhandle.parent.UserInitials = userhandle.parent.UserAddress = ""handle.save End If 起草人在起草了文章后,单击该按钮,以后对该文章的所有修改都将保留痕迹。

 Lotus Notes/Domino是一种基于Internet/Intranet技术为构架的群件系统,是构造企业信息网主要工具之一。信息检索技术作为 LotusNotes/Domino的一个主要技术,为用户提供了包括全文检索、按关键字查询、视图和文件夹等多种方式, 本文结合在实际开发中的经验体会,对Notes应用中文档查询进行讨论。 文档的查询 ---- 在LotusNotes中,信息是以文档男问奖4嬖谑菘庵械模桓鑫牡迪嗟庇诠叵敌褪菘庵械囊桓黾锹肌?也就是说,在NOTES应用中,对信息的查询就是对文档的查询,对文档的查询主要有以下几种方式:视图、文件夹、全文检索。下面我们来一一探讨他们的特点。

---- 1. 视图 ---- 视图是 LotusNotes中文档的主要浏览窗口,每一视图都包含符合一定条件的文档。当一个视图的选择条件给定以后,通过该视图所看到的文档就是符合条件的文档,如某一视图的选择条件为:Select form="请假单"; 则打开该视图后,我们所看到的文档都是请假单。视图除了有选择条件外,还可以按不同的特性将文档进行分类和排序,使得我们可以及其快捷地导航到要查找的文档。对于简单的查询,可以不编写任何程序,而通过把视图按合理的方式进行分类和排序就可以了。 Notes视图设计是应用程序设计过程中较快的一步,如果数据库第一次就设计正确,拥有在正确文档中可用的全部必需的字段,那么设计视图应是一个容易的过程,且对用户来说是直观的,可打印输出屏幕上显示的视图。

 ---- 2. 文件夹 文件夹也是文档的浏览窗口之一,但是和视图不同的是,文件夹没有选择条件,它里面的文档是通过Putinfolder来放进的,必须通过RemoveFromFolder来将其中的文档移开。

 ---- 3. 全文检索 ---- 全文检索是 LotusNotes提供的基于数据库全文索引的搜索工具,它能根据给定的检索关键字在整个数据库中搜索,并把搜索结果显示在该视图的顶端。

 ---- 4. 按关键字查询 ---- 好的查询设计应是对用户的查询给于准确快速的响应,准确,灵活地显示用户所需的数据。为满足用户多条件的组合查询,开发人员一般为用户设计一种"按关键字查询"方式,需要编写程序使用户可以输入一个或多个查询条件进行组合条件查询,找满足条件的文档。但是由于NOTES无法直接显示结果,一般的解决方法是:将查询结果存放到文件夹中,最后,打开文件夹显示查询结果。 文件夹方式存在的缺陷及解决方法 ---- 由于LotusNotes的文档是可以共享的,文件夹也可以共享,也就是说,你可以用这个文件夹来存放你的检索结果,我也可以用这个文件夹来存放我的查询结果,而且LotusNotes应该保证互相不冲突。然而遗憾的是,LotusNotes不作这个保证,导致的结果是大家互相影响,产生访问冲突。

为了解决这一问题我们提出两种方法:

 ---- 1、建立私有文件夹 ---- 所谓私有文件夹,是指文件夹属于一人私有,其他人看不见这个文件夹。可以通过创建一个"启动后私有"文件夹,每个用户使用该文件夹后,系统立即根据这个启动后私有文件夹创建一个新的属于该用户的文件夹。这样,每个用户都有一个结构完全相同而且互相不干扰的文件夹。这种解决方法保证冲突不会产生,但系统为每个用户保存一个文件夹,会导致系统维护上的困难。如果系统的用户太多,情况会更坏。如果用户注销,它的私有文件夹不会自动删除。 ---- 2、改进的视图方法 ---- 视图一般是大家共享的,我们可以通过改进视图的选择条件,将视图作为我们存储查询结果的地方,就能避免文件夹方式产生的问题。我们提出解决问题的思路是:把满足某一用户查询条件的文档作选择标记,创建一个共享视图,用视图选择公式来显示该用户的查询结果,显示或打印完成后,删除选择标记。具体实现方法如下: ---- (1) 在要查询的数据库文档表单上创建一个可编辑多值的隐含?quot;SelectedUserName"用于存放查询该文档的用户名。 ---- (2) 创建一个共享视图vwSelect,视图公式为: ---- SELECT Form = "frmFormName" & ---- @Contains(SelectedUserName;@UserName) ---- 用于显示该用户查到的文档 ---- (3) 创建一个显示视图的导航器"nvgQueryResult",其初始视图为vwSelect。 ---- (4) 用Sript语言,利用FTSearch函数进行组合条件查询,查找满足条件的文档。 编程实现 ---- (1) 查询显示 ---- 功能:完成按关键字条件查询,显示查询结果. Dim session As New NotesSessionDim db As NotesDatabaseDim doc As NotesDocumentDim view As NotesViewSet db = session.CurrentDatabaseitemvalues=item1.valuesCondition=itemvalues(0)For i=Lbound(itemvalues)+1 to Ubound(itemvalues)Condition=Condition+itemvalues(i)'记载用户输入的全部查询条件NextCount=view.FTSearch(Condition,0) '完成全文查找If count〈〉0 ThenMessagebox"本数据库中共有:"+Str(Count) + "个记录满足条件!",0+64,"提示信息" For j=1 To countSet doc=dc.getnthdocument(j)Set item = doc.GetFirstItem( "SelectedUserName" ) Call item.AppendToTextList( session.UserName ) '在域SelectedUserName中Call doc.Save( True, True )'追加用户名NextServerName = session.GetEnvironmentString("ServerName")DirName = session.GetEnvironmentString("DirectionName")DatabaseName=DirName+"DBName.nsf" '打开导航器调用视图,即显示查询结果Call workspace.OpenDatabase ( ServerName,DatabaseName,"nvgQueryResult" )ElseMessagebox "没有满足条件的记录!",0 +48,"提示信息:"End Ifend(2) 退出显示 功能:清除用户的选择标记 Dim session As New NotesSessionDim db As NotesDatabaseDim doc As NotesDocumentDim view As NotesViewSet db = session.CurrentDatabase ' ViewName = session.GetEnvironmentString("envViewNa")Set view = db.GetView(vwSelect)Set doc = view.GetFirstDocumentUserName=session.UserName'取现用户名到变量:UserNameWhile Not (doc Is Nothing ) TempValue=doc.SelectedUserName '清除文档域"SelectedUserName"中doc.SelectedUserName=""'自己的用户名;Call doc.save(True,False) '同时保留其他用户的用户名.Set item=doc.getfirstitem("SelectedUserName") Forall x In TempValue If x〈〉 UserName Then Call item.AppendToTextList(x ) Call doc.save(True,False) End If End Forall Set doc = view.GetFirstDocument Wend end

主要技术要点: ---- 以上应用设计主要运用了以下技术要点来保证显示结果的准确性和数据的共享性.

---- (1) 要查询的数据库文档表单上创建的可编辑多值的隐含域"SelectedUserName" . 域是构成表单的重要元素,对一个NOTES数据库来说,外部数据的录入要通过域, 库内存放数据显示也要依靠域。我们这里创建域"SelectedUserName"的作用是:存放查询该文档的用户名作为选择标志。 选择标志的确定:

---- 使用用户名作为选择标志主要因为在Notes中用户名是唯一的,不同的用户有不同的用户名,不会存在两个相同的用户名。这样,该域记住了有哪几个不同用户查询选择了此文档。为用视图显示文档做准备. 域的主要属性是:

 ---- 可编辑:数据可以通过按钮执行Formulas或Script来产生。

---- 隐含的:只作存储,没有显示作用。显示文档时不显示该域的数据准许多值,准许用户存入多个值,保证该域记录下选择该文档所有的用户名。因为在共享数据状态下,同一个文档同时可被多个用户查询选择,你必须记住所有选择该文档的用户名。这一点对于多用户下显示数据十分重要。 ---- (2) 视图及视图选择公式: ---- 我们设计了一个共享视图vwSelect,视图公式为: ---- SELECT @Contains(SelectedUserName;@UserName) ---- 视图功能:显示所有域SelectedUserName中含有当前的用户名的文档。 ---- @UserName:返回当前的用户名。 ---- @Contains(SelectedUserName;@UserName): ---- 用于判断是否文档域SelectedUserName中含有当前的用户名; 因为在查询时,那些满足条件文档的域"SelectedUserName"中已被加入了用户名作为选择标志,所以该视图选择显示那些域SELECTEDUSERNAME中包含用户名的文档。 ---- 在视图的设计时,使用"打开后废弃索引"选项。 小结 ---- 综上所述,LousNotes为查询应用程序开发提供了灵活快速的环境,本文所述的几种方式都能实现对数据文档的查询检索,但是,它们的实现方法和满足的目的要求不尽相同。尽管还有不足之处需要完善, 但是仍然说明了一些能结合到你的应用程序中去的Notes技术,同时,我们必须清醒地认识到它与传统关系型数据库应用程序开发系统有很大的差别,查询设计有其独特的方式,一个应用程序能很快地被设计出来并且达到可用状态。试图将传统的应用程序开发技术映射到Notes环境中的开发人员,将很难获得Notes应用程序的优点。     
版权所有2001
LotusNotes4.5是一个很好的群件工作平台,它有很好的电子邮件系统,领先的全文检索和复制功能。它还具有极强的安全措施,可以可靠地保证信息安全性。但它在解决事务处理问题上功能较弱,这时就需要传统的关系型数据库管理系统来协助解决。如何把现有的关系型数据库信息转移到Notes数据库中,以利用Notes的许多良好性能共享数据,就是本文要讨论的问题。本文以Foxpro数据库为例,介绍LotusNotes访问异种数据库的一种实现方法。LotusNotes访问异种数据库的途径LotusNotes使用ODBC标准存取异种数据库信息。通过Notes里内嵌的公式或Script语言,可在Notes文档中引入非Notes数据库信息,把现成的数据转换成Notes数据库。Notes提供以下方法来存取外部数据:1.在Notes公式里利用@Db函数Notes提供@DbColumn、@DbLookup及@DbCommand三个函数,这三个函数的第一个参数用“ODBC",就可访问异种数据库信息。但它有一个缺陷:只能按列存取信息,而不能按记录存取信息。2.。利用LotusScript数据对象LSX兼容模块使用LotusScript语言来编写存取外部数据的函数,Notes的ODBCConnection、ODBCQuery及ODBCResultSet三个类为Notes提供了用ODBC标准存取异种数据库的属性和操作。实例:1.定义数据源在存取外部数据之前,必须定义一个数据源,以便让ODBC驱动程序管理器知道怎样获取数据。一个数据源把一个特定的ODBC驱动程序和要存取的数据联系在一起,并包括要存取的数据,它与服务器或目录、后台DBMS以及网络平台相联系,这些信息都记录在一个注册文件中(在Winows95中是ODBC.INI),可采用Windows的管理工具注册数据源。在Windows95中操作步骤如下:打开Windows控制面板,按ODBC图标,击Add按钮;选择需要的驱动程序,击OK按钮;输入数据源名称、描述信息以及所需要的其它信息;有些驱动程序还需要其他一些信息,输入这些必需的信息,并击OK按钮;击Close按钮关闭ODBC配置。2.LotusNotes访问Foxpro的一种实现方法实现Notes访问Foxpro数据库的基本编程思想是:对Foxpro的一个数据库,按其结构相应地在Notes数据库里建立一个同样结构的表单,以便把Foxpro字段的信息经转换后存入Notes表单相应字段中;建立一个代理,用Script语言编写转换程序;再创建一个视图运行这个代理,以实现异种数据库信息向Notes数据库转换。现有一个Foxpro数据库BMZBK.DBF,其结构如下:字段名类型宽度说明codeCharacter7指标编码nameCharacter40指标名称fullnameCharacter60指标全称unitCharacter12计量单位3.把这个库的所有信息转换到Notes库中的实现步骤如下:1.在Notes中新建一个数据库,取名为Convert.nsf,在这个数据库里创建一个表单,取名为codelib,其内容如下:域名类型说明code文本可编辑指标编码name文本可编辑指标名称fullname文本可编辑指标全称unit文本可编辑计量单位0在Convert.nsf库中建立一个代理,取名为vfpagent,定义如下:运行此代理的时间设定为:人工选择“操作”菜单执行;指定代理操作的文档设定为:视图中所有文档。2.此代理要执行的操作,用Script编写两个事件:(1)Option事件作如下编程:OptionPublicUselsx"*LSXODBC"'存取ODBC类库的全局对象(2)Initialize事件作如下编程:SubInitializeDimsessionAsNewNotessessionDimdbAsNotesdatabaseDimdocAsnotesdocumentSetdb=session.currentdatabaseSetdoc=Newnotesdocument(db)doc.form="codelib"DimconnAsNewODBCConnectionDimqryAsODBCQueryDimresultAsODBCResultSetCallconn.Disconnect()Setqry=NewODBCQuerySetresult=NewODBCResultSet'VFR是ODBC里注册好的数据源Ifconn.ConnectTo("VFP")ThenSetqry.Connection=connqry.SQL="SELECT*fromBMZBK"'发送查询请求Setresult.Query=qryCallresult.Execute()columns=result.Columns'取出结果集,并存入Notes相应的字段中DoUntilresult.IsEndOfDatadoc.code=Trim$(result.getvalue(1))doc.name=Trim$(result.getvalue(2))doc.fullname=Trim$(result.getvalue(3))doc.unit=Trim$(result.getvalue(4))Calldoc.save(True,True)Setdb=session.currentdatabaseSetdoc=Newnotesdocument(db)doc.form="codelib"Callresult.nextrow()LoopCallconn.Disconnect()'与数据源断开连接ElseMessagebox("Couldnotconnecttoserver")EndIfEndSub3.在Convert.nsf库中建立一个视图,取名为DemoView,在此视图里创建一个操作,标题为“转换”,运行方式为简单操作:运行“vfpagent"代理。这样在打开Convert.nsf数据库时,点击DemoView视图,就会在屏幕上方出现“转换”操作按钮,点击这个按钮,就可把Foxpro一个数据库BMZBK.DBF的所有信息转换成Notes数据库了。

你可能感兴趣的:(Lotus,Notes/domino)