8.自定义函数

在报表的集成开发中，免不了会需要自定义一些业务函数，以简化开发。
自定义函数对于开发者而言，是一种抽象、归纳数据逻辑的非常好的手段。最终使用者是否能接纳自定义函数，关键要看自定义函数能否有诸如向导式帮助、实例书写参考等等方便输入的功能，如果这些问题解决不好，用户很有可能产生畏惧心理。
硕正报表自身已经有向导式帮助、实例书写参考等功能，只要求开发者以如下的XML格式、并通过调用AddUserFunctions( )函数，将自定义函数告诉控件即可：

    <?xml version="1.0" encoding="UTF-8"?">
    <Category name="函数分类1">
     <function name="函数名" >
      <usage>用途说明</usage>
      <detail>更详细的说明，可选</detail>
      <para>参数1，可选</para>
      <para>参数2，以此类推，可选</para>
      <returnDatatype>返回值的数据类型，如int、double等，可选</returnDatatype>
      <example>示例说明，可选</example>
     </function>
     <function name="函数名" >
      ...
     </function>
     ...

     <!-- 嵌套分类 -->
     <Category name="函数分类11">
      <function name="函数名" >
       ...
      </function>
     </Category>
    </Category>
    <Category name="函数分类2">
     ...
    </Category>

通过分析上述XML，可见XML文档中允许多个函数分类(即Category)，每个类别中允许有多个函数，并支持子类别的层层嵌套。
一旦控件得到该函数声明XML，就会立即建立起函数的语法词法规则、在工具箱中展示这些函数、并能提供函数的即时输入帮助。
此外，开发者也可以将函数分散在多个XML文件中，可以分多次调用AddUserFunctions( )，分别传入不同的XML文档。
总之，自定义函数功能的价值在于让最终用户方便地使用业务函数：在开发者设定的范围内，指导最终用户正确地去书写、调用，以省去很多维护、定制开发的工作量。

8.1 简单应用

自定义函数最终怎么完成计算？ 控件反向调用js!
例如，XML中声明了如下函数名:

     <function name="balance" >
      <para>编码，可选</para>
      <para>月份，可选</para>
      <returnDatatype>double</returnDatatype>
     </function>

那么在页面中必须要有同名、大小写一致的js函数:

     function balance(code, mon)
     {
       var d;
       ..(略)
       return d;
     }

如果函数在处理过程中需要调用后端数据库信息，那么可以通过 Ajax 调用(必须是"同步")后端服务方式实现.
可见普通的自定义函数应用场景，只不过是使某些固定的js()函数的调用变得方便而已，因为自定义函数最终仍由页面的同名js函数负责执行，这在“自定义函数”演示页中已经剖析殆尽。
此外，如果您在 js 函数中捕获了异常，需要在报表中显示错误信息，那么您可以返回 SoapLike异常包，其格式请参考 "公共内容"中的 “4.XML/JSON异常包”。

8.2 高级应用

8.2.1 SetBatchFunctionURL( )

有一种极端的应用场景是：用户设计的报表很大，引用自定义函数会达数百处甚至上千处，如果按照上面“7.1.简单应用”的实现方式，并且假设自定义函数都是需要通过 Ajax 来完成计算，那么，最终会导致什么后果？想必您应该知道了!
为了解决成百上千次的 Http 请求/响应带来的网络性能瓶颈，硕正报表提供了一个批处理解决方案，方案说明如下：
1.通过 js 函数 SetBatchFunctionURL( ) 设置后端批处理函数的 URL，例如:

    AF.func("SetBatchFunctionURL", "../../myfuncproc.aspx");

这个 URL 就是批计算的响应地址.
2.开发者需要开发后端的Java/Asp.Net批处理响应程序.
一旦通过 SetBatchFunctionURL( ) 设置了批处理 URL，报表在计算过程中将不再调用页面的同名自定义函数，转而采用分批的方式将函数直接交由后端执行， 分批发送的是 Http POST，并采用如下的 XML 格式:

    <Root>
     <Functions addition="附加串" >
      <!-- 第一个 -->
      <Function name="函数名">
      </Function>
      <!-- 第二个 -->
      <Function name="函数名">
       <Para>参数</Para>
       <Para dataType="double">参数</Para>    //备注: "dataType"自动采用原先所定义的(可选)
      </Function>
      <!-- 第三个 -->
      <Function name="函数名">
       <Para>参数</Para>
      </Function>
      <!-- 第 n 个等等 -->
      ...
     </Functions>
    </Root>

其中的“函数名”、“参数”表示真实的函数名、参数。“附加串”是SetBatchFunctionURL()函数中的附加参数(相当于附言，任意串，请参考SetBatchFunctionURL()函数帮助)。
后端的服务程序需要解析该XML，并完成这些函数的计算，最终按如下XML格式作为响应返回:

    <Root>
     <Functions>
      <Function>返回值</Function>
      <Function>返回值</Function>
      <Function>返回值</Function>
      ...
     </Functions>
    </Root>

备注1: <Function>的顺序必须和前面请求中的XML保持一致;
备注2: <Function>的返回值，硕正默认是把它当作数值型的，如果您希望返回 string 型，必须在串的前后加单引号;
3.异常处理
如果服务器端出现异常，建议将这个返回串按照文档 "公共内容"中的 “4.XML/JSON异常包” 的格式作封装。
如果服务器端在处理中，仅其中的某个或某几个函数出现异常，那么可以对 <Function> 单独封装, 具体可这样写：

    <Root>
     <Functions>
      <Function>返回值</Function>
      <Function>返回值</Function>
      <Function>
       <fault>
        <faultcode>错误代码</faultcode>
         <faultstring>错误</faultstring>
         <detail>详细描述</detail>
         <reason>错误原因</reason>
         <faultactor>错误角色</faultactor>
       </fault>
      </Function>
      ...
     </Functions>
    </Root>

备注: 其中<fault> ... </fault>是 SoapLike 异常包, 表示某个函数在服务器端捕获的错误(如异常)，需要在报表的计算信息窗中加以显示;

8.2.2 前后端混合计算

有时候并不是所有自定义函数都必须推送到后端计算，可能有部分函数需要在本地页面中计算，那么只要修改XML的定义，增加<RunAt>内容：

    <?xml version="1.0" encoding="UTF-8"?">
    <Category name="函数分类">
     <function name="函数名" >
      <usage>用途说明</usage>
      <detail>更详细的说明，可选</detail>
      <runAt>Local</runAt>
     ...

凡是runAt被定义为 "Local"(或Client)的函数，都不会被发送到后端，而是通过 "8.1 简单应用" 的方式简单地在客户端计算. runAt的默认值是"Server"，即后端计算.

8.3 参数输入向导

硕正报表从1.0.64.0开始，增加了函数的向导式输入功能(输入框左侧的f(x)小按钮)：
为了方便自定义函数的输入，我们对自定义函数的XML规范作了扩充，使得参数在输入时能支持下拉选择，例如下图所示的效果：
扩充后的自定义函数XML规范增加了如下内容：
1.XML增加了<Droplists>下拉资源库节点
XML中须增加<Droplists>节点，<Droplists>语法和硕正Treelist树列表、Freeform自由表头中的<Droplists>完全一致，请参考其XML文档规范，也可以参考自定义函数演示页中的XML例子.
2.参数<Para>增加下拉相关属性
增加3个属性, 例如<Para dataType="?" edittype="?" droplistID="?" >参数</Para>，其含义和可用值如下：
datatype - 可以为int, string;
edittype - 可以为droplist、editAbleDroplist、dropTreelist、editAbleDropTreelist，含义请参考树列表或自由表头文档资料;
droplistId - 指向下拉资源库的Id;
如果您开发过Treelist或freeform，会发现该规则很简单，相当于把Treelist、freeform的下拉语法直接搬过来了.