#  插件开发
> 使用应用开发平台的设计器开发业务对象，全程配置，简单易学，但不够灵活。配置业务对象时使用的业务语意，需要预先定义，遇到未考虑的业务场景，则无法处理。业务对象设计器，目标是实现常见的80%业务语意，利用开放的插件开发，实现剩余20%的功能。
## 插件如何工作

插件可以在适当的时机，根据接收到的上下文信息，对系统功能进行控制。适当的时机，是指插件只会在系统功能运行到了特定时刻，才能收到系统通知，进行功能处理；并不能对系统功能的全过程进行干预。插件拿到的上下文信息，能够调用的控制方法，也是经过系统封装，有所限制;简单的说，插件可以在系统约束的框架之内，对系统进行适度的干预，在灵活性与安全性之间有个平衡。如下图，表单界面在加载、展示期间，关键功能执行时，调用插件接口方法，触发插件事件，通知插件工作；而插件能够在被调用的事件方法中，调用表单视图、控件模型、表单数据模型，对表单界面进行控制。


  ![DataSet](image\01.png)

## 如何开发插件

### 步骤一：确定应用场景,选择插件基类

不同的业务对象类型，提供特定的业务功能，有适用的应用场景。

系统为各种业务对象类型、各种应用场景，封装了相应的插件接口、事件方法，定义了抽象插件基类，实现最基本的插件接口。

进行业务功能设计时，首先需要根据业务需求特点，分析业务应用场景，选择业务对象类型；

如果需要进行插件开发，则根据前面确定好的业务对象类型及应用场景，从下表中选择对应插件基类进行扩展：

<table>
  <tr>
    <td>业务对象类型</td>
    <td>应用场景</td>
    <td>预置的Java插件基类</td>
  </tr>
  <tr>
    <td rowspan="2">动态表单</td>
    <td>PC端界面</td>
    <td>AbstractFormPlugin</td>
  </tr>
  <tr>
    <td>移动端界面</td>
    <td>AbstractMobFormPlugin</td>
  </tr>
  <tr>
    <td rowspan="10">单据、基础资料</td>
    <td>PC端界面</td>
    <td>AbstractBillPlugIn</td>
  </tr>
  <tr>
    <td>移动端界面</td>
    <td>AbstractMobBillPlugIn</td>
  </tr>
  <tr>
    <td>单据列表</td>
    <td>AbstractListPlugin</td>
  </tr>
  <tr>
    <td>左树右表单据列表</td>
    <td>AbstractTreeListPlugin</td>
  </tr>
  <tr>
    <td>树形基础资料列表</td>
    <td>StandardTreeListPlugin</td>
  </tr>
  <tr>
    <td>移动端单据列表</td>
    <td>AbstractMobListPlugin</td>
  </tr>
  <tr>
    <td>业务操作</td>
    <td>AbstractOperationServicePlugIn</td>
  </tr>
  <tr>
    <td>单据转换</td>
    <td>AbstractConvertPlugIn</td>
  </tr>
  <tr>
    <td>关联反写</td>
    <td>AbstractWriteBackPlugIn</td>
  </tr>
  <tr>
    <td>生成凭证</td>
    <td>AbstractBuildVchPlugin</td>
  </tr>
  <tr>
    <td>报表</td>
    <td>界面</td>
    <td>暂缺</td>
  </tr>
  <tr>
    <td></td>
    <td>取数</td>
    <td>暂缺</td>
  </tr>
</table>

### 步骤二：确定事件源与控件

有交互界面的应用场景（如表单、单据列表等），在界面加载、关闭时，会触发相应的插件事件；

另外，用户与界面，以及界面上的控件交互时，也会触发插件事件。

各种控件有自己的功能特点，适用不同的业务需求，提供相应的插件事件；

在进行功能设计时，需要根据业务需求，选用合适的控件，确定需要重写的插件接口方法，响应插件事件。

后面的控件与字段章节，将详细列出各种控件的功能特定及支持的插件事件，供您参考。

**特别说明**：没有交互界面的应用场景（如单据操作、单据转换、关联反写、生产凭证等），不会与用户发生交互，其插件事件是由服务引擎按顺序触发的。

这些应用场景，不需要关注事件源、控件；只需要根据业务需求，捕获合适的插件事件即可。

### 步骤三：响应插件事件
系统封装了各种插件事件接口；

表单、控件、字段等事件源，各自有选择的支持了部分插件事件接口。

系统会在适当的时机，调用插件事件接口的方法，传入上下文参数，触发插件事件。

比如用户与前端界面、控件交互时，系统会把交互请求传递给表单、控件；表单、控件调用其下的插件事件接口实例（即插件）的方法，触发插件事件。

不同的插件事件，触发的时机、传入的上下文参数、可以控制的功能，差异非常大。

因此，必须根据业务需求，选择合适的插件事件响应。

后文会详细介绍各种应用场景、字段、控件，提供的插件事件，及其触发时机、事件参数。

## 业务插件如何响应（捕捉）插件事件

### 无交互界面的场景
没有交互界面的应用场景，插件基类已经实现了必要的插件接口，插件只需要扩展插件基类，重写事件方法即可完成事件的捕捉：
```java
package kd.bos.metadata.botp;

import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
import kd.bos.entity.plugin.args.BeforeOperationArgs;

public class WriteBackRuleOpPlug extends AbstractOperationServicePlugIn {
	@Override
	public void beforeExecuteOperationTransaction(BeforeOperationArgs e) {
		// TODO : 在此添加业务逻辑
	}
}

```
说明：插件基类AbstractOperationServicePlugIn已经实现业务操作插件接口，插件直接重写需要响应的事件方法即可。

### 有交互界面的场景
有交互界面的应用场景，插件基类实现了表单界面支持的插件接口，但没有实现各种控件的插件接口。

如果只是要捕获表单界面事件，则扩展插件基类，重写表单事件方法即可。<br />
如果要捕获各种控件事件，则需要：
- 在插件类定义，实现控件支持的插件事件接口,例如树形控件支持的节点勾选插件事件接口TreeNodeCheckListener；
- 实现控件的插件事件接口中的方法，完成事件的捕捉；
-  在表单界面插件registerListener事件，向控件实例注册本插件实例，完成控件与插件实例的绑定：控件事件发生时，即触发其所绑定的插件事件；
如下例，插件需要响应树形控件的节点勾选treeNodeCheck事件：

```java
package kd.bos.plugin.sample.dynamicform.pcform.control.template;
import java.util.EventObject;
import java.util.List;
import kd.bos.dataentity.utils.StringUtils;
import kd.bos.form.control.TreeView;
import kd.bos.form.control.events.TreeNodeCheckEvent;
import kd.bos.form.control.events.TreeNodeCheckListener;
import kd.bos.form.plugin.AbstractFormPlugin;

public class TreeViewTreeNodeCheck extends AbstractFormPlugin implements TreeNodeCheckListener {
	
	private final static String KEY_TREEVIEW1 = "treeviewap1";

	@Override
	public void registerListener(EventObject e) {
		super.registerListener(e);
		
		// 侦听树节点勾选事件
		TreeView treeView = this.getView().getControl(KEY_TREEVIEW1);
		treeView.addTreeNodeCheckListener(this);
	}

	@Override
	public void beforeBindData(EventObject e) {
		super.beforeBindData(e);
		TreeView treeView = this.getView().getControl(KEY_TREEVIEW1);
		treeView.setMulti(true);	// 支持多选
	}

	@Override
	public void treeNodeCheck(TreeNodeCheckEvent arg0) {
		TreeView treeView = (TreeView) arg0.getSource();
		if (StringUtils.equals(treeView.getKey(), KEY_TREEVIEW1)){
			List<String> selectNodeIds = treeView.getTreeState().getCheckedNodeIds();
			// TODO 在此添加业务逻辑
		}
	}
}
```
说明：插件基类AbstractFormPlugin没有实现树形控件的节点点击事件接口TreeNodeCheckListener，插件需要自行实现。

## 什么是表单视图模型、控件编程模型？

- 金蝶云苍穹，是B/S结构的，使用不同的客户端（PC端、移动端），通过网络连接到统一的服务端。

- 用户看到的交互界面，是运行在客户端的，而业务逻辑和业务插件，则运行在服务端；

- 业务插件运行在服务端，没办法直接获取到客户端界面上控件句柄，不能直接控制前端控件。

- 但插件可以通过系统封装的视图模型接口IFormView，间接的访问、控制前端界面；

- 通过系统封装的各种控件代理对象，间接的访问、控制前端界面上的控件；

- 这些运行在服务端的控件代理对象，称为控件编程模型，或简称为控件、编程模型等。

- 插件可以通过this.getView()方法，获取表单的视图模型接口实例；

- 可以通过this.getView().getControl(String key)方法，获取到控件编程模型实例；

## 什么是表单数据模型

表单的界面与数据，是分离：

- 界面显示在客户端浏览器或者移动端，在服务端，系统封装了视图模型及控件编程模型来间接控制前端界面及前端控件；
- 数据存储在服务端，在服务端，系统封装了数据模型，来控制界面上的数据；

表单的数据模型，提供各种方法访问界面数据，并且持有：
- 主实体模型：MainEntityType，表单运行时元数据对象，包含：
  - 子实体：EntityType，对应单据体、子单据体等；
  - 属性：DynamicProperty，对应字段；
- 界面数据包：DynamicObject，基于主实体模型构建的一个数据字典，存储单据体、字段值；