# cap4自定义控件-PC端


cap4表单自定义控件参考开发文档 (文档默认是v7.0sp1开始支持,新增内容会备注开始支持版本号,删除内容会备注顶用版本号)

# 自定义控件介绍

A. 自定义控件增加了表单的可扩展性。
 a. 比如使用者想在页面中增加一个需求功能,例如在表单中显示一个统计页面的板块。而这样的需求在表单常规使用中很少会遇见,或者表单没有这样的标准控件。就可以通过自定义控件实现。
B. 自定义控件和表单的关系
 a. 自定义控件是寄生在表单上的单独的组件。会自己去渲染,表单不提供渲染。也可以自己通过接口和后端交互数据,不走表单的流程。
 b. 表单会给自定义控件一定范围的基础数据支持。会提供表单和自定义控件之间的数据交互API

# 一.开发规范

# 1.1 自定义控件文档结构

自定义控件项目前端可按照如下目录结构组织资源(开发文档结构没有强制规范,这只是建议,只要开发中的路径关系理顺就行)。

./                开发文件夹
├─js                      js资源
├─css                     css资源
├─icon                    图标资源
├─index.html              html资源

# 1.2 自定义控件基础规范

A.生成一个唯一标识
 a 开发者需要在前端代码中生成一个属于当前开发自定义控件的唯一标识,用于挂载到全局实现自定义控件命名空间(开发者自己维护)
B.控件代码里面的所有this指向window[1唯一标识]
C.确保有初始化入口方法(并且要配置到配置文档去)
D.建议使用闭包进行开发

# 1.3 自定义控件存在生命周期

自定义控件由入口方法调用开始加载,表单页面关闭销毁。

# 二. 自定义控件实现和交互规范

A.表单绘制的时候会根据后端设计器的视图信息,会在对应的位置生成一个DOM容器,这个容器会附上一个ID(对表单中每一个位置的自定义控件都唯一)
B.会根据自定义控件信息去加载js文件,加载完成后调用配置的入口方法 (js文件路径和入口方法都来自于配置信息)
C.调用入口方法会传入一个Object参数(三.3.自定义控件初始传入的数据解析)
D.开发者需要在入口方法后顺序执行的过程中去加载自己的css,js,并且渲染当前控件
E.自定义控件内部的各种事件,操作区域,打开文件等等,都由自定义控件内部开发代码完成
F.自定义控件需要回填其他标准控件,或者保存自己的数据,可以根据API提供的功能就行回填

# 三. 3.自定义控件初始传入的数据解析

# 3.1 入口方法传入的参数是Object数据类型,里面有5个属性

(自定义控件入口方法参数)(3.1图)

数据解析:
A.adaptation : 这个里面有提供的api,方法集合,可实现和表单页面数据之间的通信.
B.url_prefix : 这个是url前缀,可用于开发者加载css文件和js文件的路径前缀.
C.privateId : 这个是当前控件的在表单页面的唯一标识,可用于挂载控件DOM,也是调用部分API操作的必传参数,用于操作获取对应的值。
D.getData : 获取当前控件初始的所有数据

(D图,当前控件数据信息)

a. attachmentInfo : 附件信息 (里面包含附件列表和基础附件信息)
 b. attrs : 视图里面的简单控件信息
 c. auth: 权限 (‘edit’ : 编辑,’hide’: 隐藏)
 d. ctrlType : 视图里面的控件状态(这个,自定义控件的是"suiCustomControl")
 e. customFieldInfo : 自定义控件的信息,里面包含pc,移动的js信息和入口方法信息,路径信息。
 f. digitNum : 小数位长度
 g. display : 控件名
 h. enums : 控件的枚举列表
 i. extra :设计器给控件设置的样式信息
 j. fieldLength :控件内字符的限制长度
 k. fieldType :字段类型
 l. formType : 控件所在的表的类型
 m. formatType : 数据格式
 n. Formdata : 整个表单的信息
  n-1. Alldata :整个表单的所有数据包括视图信息和用户信息,主表数据,明细表数据,多视图信息
  n-2. content :表单的基础信息,包括表单数据ID,表单权限Id等等
  n-3. formdata : 表数据
  n-4. formmains : 主表数据
  n-5. formsons : 明细表数据
  n-6. model : 表单中明细表是页签还是平铺(undefined是平铺,暂时其他是页签)
 o. id :字段名
 p. inputType :控件类型(标准控件判断)
 q. isInCalculate : 是否计算(1为计算)
 r. isInCondition : 是否是条件字段
 s. isNotNull : 必填校验(1为必填)
 t. isCustomFiled : 是否是自定义控件(1是)
 u. placeHolder : 提示语言
 v. radioAlignType : 
 w. relation : 关联信息(控件公用的基础信息)
 x. relationData : 当前控件的关联数据 
 y. showValue : 显示值
 z. value : 计算值
 aa. type : 类型
 ab. valueId : 值
E. formMessage : 当前控件在表的位置信息
 a. tableCategory : 主表还是明细表(formmain = ‘主表’,formson = ’明细表’)
 b. tableName : 所在表的表名
/**
  * 示例:
  * 
  * */
 {
   tableCategory : '',
   tableName : ''
 }

# 四. Api (依赖于(三)传入的adaptation属性,具体回填格式和数据请看下面详解)

API方法名 说明 参数 参数格式 参数说明 详细介绍
childrenGetData 获取当前控件的数据 privatedId String 当前控件的唯一标识ID (A)
childrenSetData 更新保存当前控件的数据 data,privatedId Object, String messageObj是获取的控件数据对象(更新到你维护的最新值),privaredId: 当前控件的唯一标识ID(入口方法传入) (B)
ObserverEvent 用于事件发布订阅 属性方法 Function 扩展的属性方法 (C)
backfillFormControlData 用于回填控件数据 data, privatedId Object/Array, string payload: 回填的数据, privatedId 当前控件的唯一标识ID(入口方法传入) (D)
backfillFormAttachment 用于回填附件数据 data, privatedId Object/Array, string payload: 回填的数据, privatedId 当前控件的唯一标识ID(入口方法传入) (E)
callTakeFormSave 调用表单预提交方法 data, privatedId Object, string data 调用方法的参数, privatedId 当前控件的唯一标识ID(入口方法传入) (F)
(A). childrenGetData (privateId)
/**
   * 示例:
   * 说明:此接口是用于获取当前的控件的即时数据。
   * 参数:privateId->(String)唯一标识
   * */
  adaptation.childrenGetData(privateId);
(B). childrenSetData (data, privateId)
/**
   * 示例:
   * 说明:此接口用于把自定义控件操作当前控件的数据回填到表单维护的当前控件数据中,建议取得数据对象,你就维护此对象,回填的时候传入此数据。
   * 参数:data->(Object)当前控件的数据
   *    privateId->(String)唯一标识
   * */
  adaptation.childrenSetData (data, privateId);
(C). ObserverEvent
/**
   * 示例:
   * 说明:发布订阅方法,有多个属性方法
   * 属性方法:
   *    one (key,  fn) : 处理一次
   *    remove (key,  fn) : 删除注册事件
   *  listen (key , fn) : 组册事件
   *    trigger () : 触发事件,第一个传入key,后面的传入需要传递的数据
   * 参数:
   *  key->(String) 事件名
   *    fn->(Function) 回调方法,里面可能有参数,看事件的约定
   * */
  adaptation.ObserverEvent.one('key', function(params) {});
  adaptation.ObserverEvent.remove('key', function() {});
  adaptation.ObserverEvent.listen('key', function(params) {});
  adaptation.ObserverEvent.trigger('key', params);
(D). backfillFormControlData(data, privateId)
/**
   * 示例:
   * 说明:此接口是用于自定义控件回填表单其他控件数据
   * 参数:data -> (Object || Array) 组织的回填的数据
   *         Object格式 :
   *     {
   *       tableName : (表名),
   *       tableCategory : (表类型(formmain || formson)),
   *      updateData : {
   *         filedName(表单对应控件名) :{} (回填控件的数据对象)
   *       },
   *       updateRecordId : (更新对应的数据行Id,主表控件没有,明细表控件有)
   *     }
   *       Array格式:
   *     [
   *       {
   *         tableName : (表名),
   *         tableCategory : (表类型(formmain || formson)),
   *         updateData : {
   *           filedName(表单对应控件名) :{} (回填控件的数据对象)
   *         },
   *         updateRecordId : (更新对应的数据行Id,主表控件没有,明细表控件有)
   *       }
   *     ]
   *      privateId->(String)唯一标识
   * 注意:
   *  1.回填是根据后端控件值格式
   *  2.回填输的数据对象里面有三个值很特殊 :从入口方法获取的前端控件对象里面三个属性名是showValue, value, valueId.对应的回填对象里面格属性名是showValue,showValue2, value (非常重要)
   *  3. 2里面获取的前端控件数据对象里面的属性名和回填控件的数据对象属性名 showValue = showValue, value = showValue2, valueId = value  (非常重要)
   * */
   adaptation.backfillFormControlData (data, privateId);
(E). backfillFormAttachment(data, privateId)
/**
   * 示例:
   * 说明:此接口用于回填表单里面控件的附件
   * 参数:data -> (Object || Array) 组织的回填附件数据
   *       Object格式:
   *         {
   *           tableName : (表名),
   *           tableCategory : (表类型),
   *           updateRecordId : (更新对应的数据行Id,主表控件没有,明细表控件有),
   *           handlerMode : (操作类型值:添加add和删除delete),
   *           fieldName : (表单回填的对应控件名),
   *           addAttchmentData : [{}], // 回填控件的附件信息列表,附件信息对象需要经过后端对应的格式生成
   *           deleteAttachmentData :[] // 回填控件附件列表中要删除的对应Id数组
   *         }
   *       Array格式:
   *         [
   *           {
   *             tableName : (表名),
   *             tableCategory : (表类型),
   *             updateRecordId : (更新对应的数据行Id,主表控件没有,明细表控件有),
   *             handlerMode : (操作类型值:添加add和删除delete),
   *             fieldName : (表单回填的对应控件名),
   *             addAttchmentData : [{}], // 回填控件的附件信息列表,附件信息对象需要经过后端对应的格式生成
   *             deleteAttachmentData :[] // 回填控件附件列表中要删除的对应Id数组
   *           }
   *         ]
   *      privateId->(String)唯一标识
   * 注意:
   *  1.handlerMode属性有两个值必填其一
   *  2.handlerMode属性为‘add’ => 只会去读取addAttchmentData属性列表,所以必填
   *  3.handlerMode属性为‘delete’ => 只会去读取deleteAttachmentData属性数组,所以必填
   * 
   * 
   * */
  adaptation.backfillFormAttachment (data, privateId);
F. callTakeFormSave(data, privateId)
/**
   * 示例
   * 说明:此接口用于自定义控件调用预提交
   * 参数:data -> (Object) 参数对象
   *      {
   *        type: 'save', // 必填,值固定
   *        isPrev: true, // 必填,值固定
   *        callback :(数据格式为Function), // 回调方法,有一个参数,参数格式为Object
   *        successFn :(数据格式为Function), // v7.0 sp2-1130版本支持,正确预提交回调方法
   *        errorFn :(数据格式为Function), // v7.0 sp2-1130版本支持,预提交失败,或者错误回调方法
   *      }
   *      privateId->(String)唯一标识
   * 
   * 注意:
   *  1.数据格式按规则填写
   *  2.只是预提交表单数据
   *  3.只是只支持这些
   * 
   * */
  adaptation.callTakeFormSave (data, privateId);

# 五. 表单触发的事件(依赖于adaptation.ObserverEvent)

监听事件名 说明 参数 参数格式 参数说明 详细介绍
Event + 唯一标识 用于监听是否数据更新 data Object 返回的是表单中当前控件最新数据 (A)
Save + 唯一标识 通知自定义控件表单要保存了 (B)
(A). Event + 唯一标识:用于告诉对应的自定义控件表单里面当前的数据更新了
/**
   * 示例:
   * 说明: 于告诉对应的自定义控件表单里面当前的数据更新了
   * 事件名: 字符串 'Event' + 入口方法传入的唯一标识privateId
   * 参数:data => (Object) 
   *         当前控件的控件数据对象
   * 
   * */
  adaptation.ObserverEvent.listen('Event' + privateId, function(data) {
    // 执行你的操作
  });
(B). Save + 唯一标识:用于告诉对应的自定义控件要保存了        (v7.0 sp2版本支持)
/**
   * 示例:
   * 说明:用于告诉对应的自定义控件当前表单要保存了
   * 事件名:字符串 'Save' + 入口方法传入的唯一标识privateId
   * 参数: privateId => (String) 唯一标识
   *        data => (Object)
   * */
  adaptation.ObserverEvent.listen('Save' + privateId, function(privateId, data) {
    // 执行你的操作
  });

# 六. 前端开发示例

下面是一个前端自定义控件开发示例 (按钮自定义控件)
/**
  * 说明: 是一个按钮类自定义控件的示例
  * */
 (function(factory){
  var nameSpace = 'field_5902128098173592526';
  if(!window[nameSpace]){
   var Builder = factory();
   window[nameSpace] = {
    instance: {}
   };
   window[nameSpace].init = function (options) {
    window[nameSpace].instance[options.privateId] = new Builder(options);
   };
   window[nameSpace].isNotNull = function (obj) {
    return true;
   };
  }
 })(function(){
  /**
  * 构造函数
  * @param options
  * @constructor
  */
  function App(options) {
   var self = this;
   //初始化参数
   self.initParams(options);
   //初始化dom
   self.initDom();
   //事件
   self.events();
  }
  
  App.prototype = {
   initParams : function (options) {
    var self = this;
    self.adaptation = options.adaptation;
    self.privateId = options.privateId;
    self.messageObj = options.getData;
    self.preUrl = options.url_prefix;
   },
   initDom : function () {
    var self = this;
    dynamicLoading.css(self.preUrl + 'css/formQueryBtn.css');
    self.appendChildDom();
   },
   events : function () {
    var self = this;
    // 监听是否数据刷新
    self.adaptation.ObserverEvent.listen('Event' + self.privateId, function() {
     self.messageObj = self.adaptation.childrenGetData(self.privateId);
     self.appendChildDom();
    });
   },
   appendChildDom : function () {
    var self = this;
    var domStructure = '<section class="customButton_box_content">'+
     '<div class="customButton_class_box '+ self.privateId + '" title="' + self.messageObj.display.escapeHTML() + '">'+ self.messageObj.display.escapeHTML() +'</div>'+
     '</section>';
    document.querySelector('#' + self.privateId).innerHTML = domStructure;
    var jumpFun = function() {
     // 事件执行
    };
    document.querySelector('.' + self.privateId).removeEventListener('click',jumpFun);
    document.querySelector('.' + self.privateId).addEventListener('click',jumpFun);
    //渲染隐藏权限
    if (self.messageObj.auth === 'hide') {
     document.querySelector('#' + self.privateId).innerHTML = '<div class="cap4-text__browse" style="line-height: 1.8; color: rgb(0, 0, 0) !important;">***</div>';
    }
   },
   dealCdtMapping : function (opt) {
    $.ajax({
     url: '/seeyon/rest/cap4/formquerybtn/dealCdtMapping?formId=' + opt.formId + '&fieldName=' + opt.fieldName + '&formDataId=' + opt.formDataId + '&formSubDataId=' + opt.formSubDataId + '&designId=' + opt.designId,
     success: function (data) {
      if(opt.callback && typeof opt.callback === 'function')
       opt.callback.apply();
     },
     error: function (e) {
      if(e.message){
       top.$.error(e.message);
      }else{
       top.$.error('处理筛查条件出错...');
      }
     }
    });
   }
  };

  var dynamicLoading = {
   css: function(path) {
    if(!path || path.length === 0) {
     throw new Error('argument "path" is required !');
    }
    var head = document.getElementsByTagName('head')[0];
    var link = document.createElement('link');
    link.href = path;
    link.rel = 'stylesheet';
    link.type = 'text/css';
    head.appendChild(link);
   },
   js: function(path) {
    if(!path || path.length === 0) {
     throw new Error('argument "path" is required !');
    }
    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.src = path;
    script.type = 'text/javascript';
    head.appendChild(script);
   }
  }

  return App;
 });

# 七. 自定义控件调试环境和与表单的交互

1.协同开发环境.
2.我们会持续维护表单和自定义控件之间的交互api,如有特殊情况可以联系后讨论处理方案

# 八.常见的错误

1.this的指向问题
2.同一个表单里面可能具有多个相同的自定义控件
 1) 需要当前控件防污染处理
3.兼容
 1) 因为兼容IE(定义变量使用var (不建议let , const), 不建议ES5以上标准的js书写代码)
 2) 也可以是最终运行的代码是编译成ES5标准的(例如使用:babel-polyfill)
4.事件初始化
 1) 初始化方法调用后要对原有注册的方法进行销毁处理,并重新注册
5.数据陈旧
 1) 没有监听(五-A)的监听方法,去更新当前控件里面的数据
 2) 如果没有第一步,新操作的时候也没有去取新的数据
 3) 如果没有第一步,页面数据变化了自定义控件没有刷新渲染,出现了运行态和预期不一致

End

创建人:yinyanting