# CAP4自定义控件 - 移动端
* * *
CAP4移动端表单自定义控件基本开发文档
CAP4表单自定义控件开发源自于PC端自定义控件,为了尽量保持一致开发,CAP4移动端自定义控件开发规范与[PC端自定义控件开发规范](http://mall.seeyon.com/help/Public/dev/index.html?0.589103825843704#custom-custom)基本保持一致。
## 1\. 开发规范
### 1.1. 自定义控件项目结构
自定义控件项目前端可按照如下目录结构组织资源(目录结构及命名非强制要求),可根据实际情况增删目录。
~~~
─my-widget 组件资源存放的目录名
├─js js资源
├─css css资源
├─icon 图标资源
├─index.html html资源
~~~
### 1.2. 自定义控件生命周期
自定义控件必须实现init及destroy接口,负责自定义控件的渲染及销毁
* 自定义控件渲染
~~~
a. 表单渲染时调用自定义控件实现的init接口来渲染自定义控件
b. 调用init接口时主动表单会传递以下参数
c. 可以根据需要动态/按需加载控件所需的其他资源
myWidget.init({
adaptation, // 表单操作代理
url_prefix, // 控件资源目录存放位置,如1.1.中的my-widget
privateId // 控件的标识符
})
~~~
* 自定义控件销毁
~~~
表单会在某些情况(如视图切换/保存新建/保存复制)下需要调用自定义控件的destroy接口销毁自定义控件
myWidget.destroy(privateId)
~~~
### 1.3. 自定义控件开发
* 自定义控件命名空间
~~~
a. CAP4表单在渲染自定义控件时会使用定义的命名控件获取自定义控件接口,用于管理自定义控件生命周期。
b. 命名空间建议以"cap4_custom_widget_"作为前缀,后缀根据控件自身语义命名
c. 命名空间目前默认注册到window对象,请尽量按照建议做法避免命名冲突
~~~
*示例:*
~~~javascript
var scopeName = "cap4_custom_widget_my_widget_name";
var widgetImpl = {};
window[scopeName] = widgetImpl;
~~~
* 自定义控件渲染及事件交互
~~~
a. 组件必须实现init及destroy方法
b. init方法用于组件初始化(如组件渲染、事件绑定、更新渲染等)
c. destroy方法用于组件销毁(如解除事件绑定、释放内存)
d. 以上两个接口均由表单发起调用
~~~
*示例:*
~~~javascript
var scopeName = "cap4_custom_widget_my_widget_name";
var widgetImpl = {
init: function(options) {
// 自定义初始化/渲染/事件绑定
},
destroy: function(widgetId) {
// 自定义控件销毁
}
};
window[scopeName] = widgetImpl;
~~~
* 获取自定义控件数据
~~~
通过init接口传入的adaptation.childrenGetData方法获取控件数据
var field = adaptation.childrenGetData(privateId)
field = {
attachmentInfo : 自定义控件附件信息,
attrs : 自定义控件属性样式,
auth : 控件权限 追加(add) 编辑(edit) 浏览(browse) 隐藏(hide),
ctrlBorderStyle :,
ctrlTitleStyle: ,
ctrlType : 控件类型,
customFieldInfo : 自定义控件的自定义控件信息,
display : 控件名,
enums : 控件的枚举信息,
fieldLength : 字段长度,
fieldType : 字段类型,
formType: 表单类型 (formmain : 主表, formson : 明细表),
recordId: 明细表的行ID,
id: 字段ID(这里是字段名),
inputType: 控件类型,
isInCalculate: 是否计算(‘1’: 计算, ‘0’: 不计算),
isInCondition: ,
isNotNull: 是否必填,
placeHolder: 提示信息,
relation: ‘关联信息’,
relationData: {
imgShow: 是否显示关联图标,
viewThrough :"1"//是否可穿透
toMasterDataId:"2222"//穿透的数据id 无流程指的是表单数据id 有流程就是summeryId
},
showValue: 显示数据信息,
value:参与计算的值,
valueId :数据库存储值
formdata : { // 表单信息 (这之前都是当前控件信息)
content : 表单的基础信息(包括表单的权限Id, 表单Id的等),
formmains : 表单的主表数据,
formsons : 表单的明细表数据,
model : 明细表是页签模式还是平铺模式(平铺模式是default),
allData : 整个表单的所有数据
}
}
~~~
* 监听自定义控件数据变化
~~~
a. 表单计算等可能引起自定义控件数据
b. 使用init接口传入的ObserverEvent.listen方法监听自定义控件数据变化并根据情况作出更新
adaptation.ObserverEvent.listen('Event' + privateId, function(newField) {
// 更新你的控件
})
~~~
* 自定义控件回填/更新表单数据
| 方法 | 说明 | 参数 | 参数格式 | 参数说明 |
| --- | --- | --- | --- | --- |
| backfillFormControlData | 用于回填控件数据 | (payload, privatedId) | (Object/Array, String) | payload: 回填的数据, privatedId 当前控件的唯一标识ID(入口方法传入) |
| backfillFormAttachment | 用于回填附件数据 | (payload, privatedId) | (Object/Array, String) | payload: 回填的数据, privatedId 当前控件的唯一标识ID(入口方法传入) |
~~~javascript
// 回填数据示例 - 数组
payload = [
{
tableName : String,
tableCategory : String,
updateData : {
field0002 : {
value : 'your value'
...
},
field0003 : {
value : 'your value'
...
}
},
updateRecordId : String
}
]
// 回填数据示例 - 对象
payload = {
tableName : String,
tableCategory : String,
updateData : {
field0002 : {
value : 'your value'
...
},
field0003 : {
value : 'your value'
...
}
},
updateRecordId : String
}
~~~
* 自定义控件调用表单方法
~~~
可以通过adaptation.backCallApi(name, callback, payload)方法在需要时主动调用表单公开的方法。
name: String 方法的名称
callback: Function 方法调用完成之后的回调(可选)
payload: Any 表单方法接受的参数,由对应的方法决定
~~~
*支持的方法列表*
| 名称 | 参数 | 说明 |
| --- | --- | --- |
| presave | { needCheckRule: '0', needDataUnique: '0', needSn: '0' } | 表单预提交, 将用户填写的表单数据写入后端缓存 |
*调用示例*
~~~javascript
adaptation.backCallApi(
'presave',
function() {
console.log('表单预提交成功');
},
{ needSn: '1' }
)
~~~
## 2\. 开发示例
~~~
下面以实现一个按钮点击弹出"Hello CAP4!"例子演示如何开发自定义控件
~~~
*示例:*
~~~javascript
(function () {
// >>> step1. 定义控件唯一标识(命名空间)
var scopeName = "field_5902128098173592526";
// >>> step2. 实现自定义控件渲染及交互逻辑
var MyWidgetImpl = function (options) {
this.customWidgetType = scopeName;
this._create(options);
};
MyWidgetImpl.prototype = {
constructor: MyWidgetImpl,
_create: function (options) {
this._options = options;
this._widgetId = options.privateId;
this._widgetProxy = options.adaptation;
this._wrapper = document.getElementById(this._widgetId);
this._field = this._widgetProxy.childrenGetData(this._widgetId);
this._tunnelId = 'Event' + this._widgetId;
if (!this._wrapper) return console.warn('没有找到组件渲染的容器');
this.clicked = 0;
this._update();
console.log(this._widgetProxy);
},
_update: function (field) {
if (field) this._field = field;
this._destroy();
this._buildRendering();
this._postCreate();
},
_buildRendering: function () {
var elemId = 'section-' + this._widgetId;
var btnId = 'section-' + this._widgetId + '-btn';
var tpl = '<section id="section-' + elemId + '">' +
'<button id="' + btnId + '" style="display:block;border:1px solid blue;border-radius:5px;margin:8px 16px;padding:5px;line-height:24px;;text-align:center">' + this._field.display +
'clicked ' + this.clicked + '次</button>' +
'</section>';
this._wrapper.innerHTML = tpl;
this._el = this._wrapper.querySelector('#' + elemId);
this._btnEl = this._wrapper.querySelector('#' + btnId);
},
_postCreate: function () {
this._unbindTap = this._bind(this._btnEl, 'tap', this.onClick.bind(this));
this._unbindUpdate = this._listen(this._tunnelId, this._update.bind(this));
},
_destroy: function () {
this._unbindTap && this._unbindTap();
this._unbindUpdate && this._unbindUpdate();
this._el = this._btnEl = null;
},
_bind: function (el, evt, cb) {
el.addEventListener(evt, cb, false);
return function () {
el.removeEventListener(evt, cb, false);
}
},
_listen: function(evt, cb) {
var hub = this._widgetProxy.ObserverEvent;
hub.listen(evt, cb);
return function() {
hub.remove(evt, cb);
}
},
onClick: function () {
this.clicked += 1;
this._btnEl.innerHTML = this._field.display +
'clicked ' + this.clicked + '次';
alert('Hello CAP4!');
this._update();
}
};
// >>> step3. 实现开发规范约定的init及destroy接口
var myWidgets = {};
// 组件初始化
MyWidgetImpl.init = function (options) {
var widget = new MyWidgetImpl(options);
myWidgets[ widget._widgetId ] = widget;
};
// 组件销毁
MyWidgetImpl.destroy = function (widgetId) {
var widget = myWidgets[ widgetId ];
if (widget) widget._destroy();
delete myWidgets[ widgetId ];
};
// >>> step4. 注册组件实现到命名控件
window[ scopeName ] = MyWidgetImpl;
})();
~~~
*运行效果:*
![](http://mall.seeyon.com/help/Public/dev/static/img/dev-form-sample.e645cc9.png "自定义控件开发示例")
## 3\. 注意事项
* 自定义控件有动态加载资源时,要防护重复加载资源
* 由于数据变化等引起自定义控件需要更新时,要做好事件清理解绑,防止重复绑定事件
* 如果需要使用到cmp组件,请参考[cmp组件文档](http://open.seeyon.com/seeyon/cmp2.0/doc/)
* 未列出内容,待后续更新补充
* * *
*End*
- 概要
- 技术介绍
- 框架与环境
- vue开发
- 开发规范
- 前端开发规范
- 总体原则
- HTML规范
- HTML&css规范
- vue编码规范
- Javascript规范
- 后端开发规范
- cap4
- 自定义控件
- 前端2.0(PC+移动)
- PC前端
- 后端
- 移动端
- 移动端接口
- 低版本协同升级到V5 8.0适配说明
- 自定义按钮
- 自定义按钮(无流程)
- 自定义控件(列表插槽)
- 自定义按钮(筛选条件)
- 低版本协同升级到V5 8.0适配说明
- 门户空间
- 门户与栏目挂载
- 栏目开发及流程说明
- 页面模板
- 客开通路及插件体系
- 表单设计器扩展配置
- 使用步骤
- 配置说明
- 事件API
- Demo示例
- 运行态客开通路
- 插件使用步骤
- 插件接口
- 事件接口
- 钩子相关接口
- 表单操作接口
- Demo示例
- 插件机制
- 表单运行态接口(旧)
- 白名单插件
- 版本记录
- vue组件库
- 开发指南
- 开发文档规范
- 业务组件介绍
- 业务组件
- table组件
- 分页组件
- title组件
- 统计排队组件
- code组件
- 条件筛选
- 批量导入
- 上传Excel
- 批量更新
- 批量刷新
- UI组件
- 按钮组件
- 复选组件
- 取色器组件
- 示例组件
- 水平选择组件
- 选图标组件
- 提示组件
- 单选组件
- 搜索组件
- 选择组件
- 穿梭框组件
- 标签组件
- 文本组件
- 树组件
- 验证组件
- 菜单组件
- iframe组件
- toolbar
- 统计组件
- 饼图
- 柱状图
- 图标
- 业务关系开发指南
- 自定义触发
- 自定义关联
- 后端API
- 更新表单数据缓存
- 发起表单流程
- 取得指定表单PDF或截图
- 无流程批量添加
- 无流程批量删除
- 无流程批量更新
- 无流程批量导出
- 客开培训文档
- Vue基础培训
- Vue实战培训
- Vue进阶培训
- VueCLI3培训
- cap3
- 自定义控件
- 后端
- 移动端
- 前端编译
- 表单运行态接口
- 协同云