# CAP4自定义控件 - 移动端


CAP4移动端表单自定义控件基本开发文档

CAP4表单自定义控件开发源自于PC端自定义控件,为了尽量保持一致开发,CAP4移动端自定义控件开发规范与PC端自定义控件开发规范 (opens new window)基本保持一致。

# 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对象,请尽量按照建议做法避免命名冲突

示例:

var scopeName = "cap4_custom_widget_my_widget_name";
  var widgetImpl = {};
  window[scopeName] = widgetImpl;
  • 自定义控件渲染及事件交互
a. 组件必须实现init及destroy方法
b. init方法用于组件初始化(如组件渲染、事件绑定、更新渲染等)
c. destroy方法用于组件销毁(如解除事件绑定、释放内存)
d. 以上两个接口均由表单发起调用

示例:

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(入口方法传入)
// 回填数据示例 - 数组
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' } 表单预提交, 将用户填写的表单数据写入后端缓存

调用示例

adaptation.backCallApi(
  'presave',
  function() {
    console.log('表单预提交成功');
  },
  { needSn: '1' }
)

# 2. 开发示例

下面以实现一个按钮点击弹出"Hello CAP4!"例子演示如何开发自定义控件

示例:

(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;
})();

运行效果:

自定义控件开发示例

# 3. 注意事项

  • 自定义控件有动态加载资源时,要防护重复加载资源
  • 由于数据变化等引起自定义控件需要更新时,要做好事件清理解绑,防止重复绑定事件
  • 如果需要使用到cmp组件,请参考cmp组件文档 (opens new window)
  • 未列出内容,待后续更新补充

End

创建人:yinyanting