本文希望通过解决以下疑问,从根本上告知大家如何调试的问题:

1,smartbi是如何加载js、css文件的?

2,是如何实现与服务端异步或同步沟通的?

3,在smartbi添加一个拥有复杂界面(有交互事件)的弹窗如何实现?

4,面对异步请求、鼠标事件等存在多浏览器问题,smartbi是如何处理的,有没有提供一些工具类方法

5,smartbi里面的每个交互界面,是如何衔接在一起的,每个界面是个独立的jsp还是通过js动态衔接的?

1、Smartbi前端框架

是典型的基于JavaScript的面向对象框架,整个系统只有几个入口jsp(譬如index.jsp、login.jsp),剩下基于AJAX按业务或操作逻辑按需动态加载或注销组件,譬如在系统中双击一张灵活分析报表,系统就会调出灵活报表组件(QueryView)并执行打开的操作。下图是简单的前端组件图,对于定制来说最常做的操作是编写或修改业务逻辑层的组件。

上图分为四层:

底层组件:是工具类、抽象接口类性质,下面章节会重点介绍几个常用的

基础控件:类似下拉框、表格、树、tab、列表、弹窗等界面控件或对象

业务控件:基于基础控件又封装的一层具有业务意义的控件,譬如定制管理左侧的资源树就是资源树控件

业务逻辑:系统的每个功能,其实都会对应一个组件,譬如灵活报表、电子表格、透视分析、多维分析等都会有自己对应的组件,通过定制给系统增加一个功能界面也相当于要创建一个业务逻辑组件。

2、关键组件介绍

2.1、JSLoader:提供加载js的方法

     smartbi内置了一个全局对象jsloader, 就是通过这个脚本对象提供的几个方法异步加载js,如果很多地方重复加载一个js,系统会自动缓存,只会从服务器请求一次。

     要能让jsloader正常加载到js,需要遵循:

     1)所有js文件必须置于“vision/js/”目录或其子目录下

     2)js文件名(不含后缀)及其所有父目录的文件名都不能包括“.”

     3) 模块内必须定义一个与文件名同名的全局变量

     jsloader中三个加载js方法说明见下文:

  • resolve(name, useGlobal):按需加载并执行
    参数说明:
    name:要加载的js名称,是相对于vision/js目录的完整路径名,譬如加载vision/js/freequery/lang/CustomEvent,就是"freequery.lang.CustomEvent"
    useGlobal:是否全局,缺省为false,如果为true,就等同于使用<script>标签加载

     //全局加载,等同于在index.jsp中使用<script>标签引用,被加载脚本中的全局对象可以直接使用
     jsloader.resolve('thirdparty.jquery.jquery', true);    
     //局部加载,等同于局部变量,另外一些不能引用这个变量的地方需要的话又得重新resolve
     var util=jsloader.resolve("freequery.common.util");
     var CustomEvent = jsloader.resolve("freequery.lang.CustomEvent"); //返回的只是freequery.lang.CustomEvent类,还没有实例化
  •  resolveMany(names):批量加载但不执行 ,意思是批量从服务端请求js,减少与服务端沟通时间                                       
    参数说明
    names:要加载的js名称数组,是相对于vision/js目录的完整路径名,如['freequery.common.util','bof.baseajax.common.Application']

    jsloader.resolveMany(['freequery.common.util','bof.baseajax.common.Application','freequery.widget.Module' ]);
    //后面如果再想使用具体的类时:
    var util=jsloader.resolve("freequery.common.util"); //这个时候实际不会发起服务端请求的,只是会从缓存里面拿到脚本并eval执行
  • imports(className):仅声明待用到再加载与执行
    参数说明
    className:要加载的js名称,是相对于vision/js目录的完整路径名,譬如:"freequery.lang.CustomEvent"

     var util = jsloader.imports("freequery.common.util");
     //用的使用需要调用getInstance()去实例化对应的对象,例如: util.getInstance().remoteInvokeEx(...)

2.2 Module2:js组件基类

完整名称:freequery.widget.Module2,可以是所有业务逻辑组件的基类,一般情况,编写界面是同名js文件和html文件配套,前者是组件业务逻辑,一般会继承freequery.widget.Module2,后者是布局文件,Module2内置了如下逻辑:

     1)使用其中的addListener和removeListener方法给dom元素注册事件

     2)使用其中的init方法,实现了界面逻辑和界面布局的分离,界面布局可以是与组件js文件同目录及同名的.html,这样系统会自动加载布局文件,同时会给布局html文件中定义了bofid的元素自动执行以下操作

  • 定义了bofid的dom元素,可以在js组件中通过this.elem+bofid,其中首字母大写引用,譬如:<span bofid=“testSpan” />,则this.elemTestSpan可以引用该元素
  • js组件可以使用以下方法给元素添加事件:elem + bofid,其中首字母大写 + 事件名称+_handler,如果在组件中添加这类规则的方法,系统会自动给对应元素加上对应鼠标事件,譬如elemTestSpan_click_handler,就是给bofid为testSpan的元素添加click事件

     3)destroy方法,注销组件

完整的示例见下面的

 

/**
 * 为一个DOM对象添加事件, 供子类调用
 * 示例:this.addListener(this.elem_btnQuery , "click", this.refreshData , this);
 * @modifier final, protected
 * @param element
 *            一个DOM对象
 * @param type
 *            待绑定的事件类型, 如'click', 'mouseup'. 注意: 不要加'on'前缀
 * @param handler
 *            处理函数
 * @param that
 *            [可选] 处理函数中this所指的对象
 * @param group
 *            [可选] 很少用到. 用于对"事件绑定"分组, 便于按组解除绑定. 见removeListenersByGroup()
 * @return void
 */
Module2.prototype.addListener = function(element, type, handler, that, group) {}

/**
 * 解除对一个DOM对象的事件绑定, 供子类调用
 * 示例:this.removeListener(this.elem_btnQuery,"click",this.refreshData);
 * @modifier final, protected
 * @param element
 *            一个DOM对象
 * @param type
 *            待绑定的事件类型, 如'click', 'mouseup'. 注意: 不要加'on'前缀
 * @param handler
 *            处理函数
 * @param that
 *            [可选] 处理函数中this所指的对象
 * @param group
 *            [可选] 绑定时给出的组名. 见addListener()
 * @return void
 */
Module.prototype.removeListener = function(element, type, handler, that, group) {}

/**
 * @param container  父容器dom对象
 * @param url html模板路径或内容,传递__url 代表与当前js组件同目录同名的html文件
 * @param noWrapper  是否创建一个父容器,true or false,缺省是false, 意思是直接用第一个参数container作为第二个参数html内容的父容器,如果为true,会再创建一个父容器包装第二个参数HTML,最后再赋给第一个参数container
 * @param noDoGet 这个是说是否需要请求html内容, true代表不用请求,也就是第二个参数传递的是html内容,false代表需要,就是第二个参数传递的是html模板路径
 */
Module2.prototype.init = function(container, url, noWrapper, noDoGet) {}

 

2.3、domutils:工具类

工具类,提供判断浏览器版本、异步请求、给dom元素增加css类等工具了i方法,详细可以查看smartbi.war/vision/js/freequery/lang/domutils.js,这里列出几个常用的方法:
  • doGet(url,notUseGBKJSP) :使用get方式请求url
    参数说明:
    url:要请求的url地址,如果是系统内部资源,是相对于vision的url,譬如:template/freequery/query/QueryView.template
    notUseGBKJSP:是否使用gbk.jsp加载,默认为false,gbk.jsp是系统用于加载js,.template,.html文件的一个jsp

 

var template = domutils.doGet("template/freequery/query/QueryView.template");
this.panel = document.createElement("div");
this.panel.innerHTML = template;
  • doPost(url, data, callback, errorHandler, scope, headers):使用post方式请求url
    参数说明:
    url:如果是系统资源,相对于vision地址的url
    data:post的数据,譬如:"A=xx&B=yy"
    callback:请求成功返回的回调函数,如果传递了此方法就是异步请求,否则同步请求
    errorHandler:请求异常的回调函数,只有传递了callback参数时,此参数才生效
    scope:callback函数内部的this对象
    headers:请求头信息,json对象,譬如{If-Modified-Since:0}
    //以下示例示意说明,并不能真的运行
    var url = "RMIServlet";	
	var data = null;
	data ="className=" + encodeURIComponent(className) + 
			"&methodName="+encodeURIComponent(methodName) + 
			"&params="+encodeURIComponent(paramsStr);
	domutils.doPost(url, data, function(responseText) {
		var export2FtpUtil = jsloader.resolve("aladdin.utils.Export2FtpUtil");
		export2FtpUtil.showExportResult(responseText);
	}, function(xhr) {
		alert("导出失败!");
	}, this, null);
  • addClassName/removeClassName (elem,value):给dom元素添加或删除css样式类

    参数说明:
    elem:dom元素对象
    value:样式类名

    if (domutils.hasClassName(elem, 'awesomplete')) {
         domutils.addClassName(elem, 'search-wrapper'); //domutils.removeClassName(elem, 'search-wrapper');
    }

     

     

addClassName : function(elem, value) {}
removeClassName : function(elem, value) {}
is+浏览器英文名代表判断是否xx浏览器,譬如isIE : function() {}
loadFile : function(url) {}
if (domutils.hasClassName(elem, 'awesomplete')) {
     domutils.addClassName(elem, 'search-wrapper');}
isIE():is+浏览器英文名代表判断是否xx浏览器

 

 

util
这个工具类提供了和服务端module(链接自定义module)直接沟通的方法、工具类,smartbi.war/vision/js/freequery/common/util.js
remoteInvokeEx : function (className, methodName, paramArray, callback, that, headers) {
remoteInvoke : function (className, methodName, paramArray, callback, that, noLookup, headers) {
/**
 * 根据名字取得Cookie
*/
getCookie : function(name)
getSystemConfig: function(key){
lang
提供了类的继承方法
extend : function(subclass, superclass) { //FROM: yahoo yui    
patch : function(subclass, superclass) {
parseJSON : function (jsonString) {    
toJSONString : function (obj) { 
CustomEvent
自定义事件,组件希望在某个点抛出事件,供外面调用组件的地方注册使用时,可以使用这个类,例如电子表格刷新完成抛出onAfterRefresh事件 :
组件中定义事件方法
this.onAfterRefresh = new CustomEvent("AfterRefresh", this);
触发事件方法:
this.onAfterRefresh.fire(this); //其中参数可以任意多个
注册事件方法
this.onAfterRefresh.subscribe(this.doParamChangeRefresh, this);
取消注册事件
this.onAfterRefresh.unsubscribe(this.doParamChangeRefresh, this);
BaseDialogEx
继承了freequery.widget.Module2,弹窗内容组件基类
BaseDialogEx.prototype.init = function(parent,data,fn,obj,win){
BaseDialogEx.prototype.destroy = function(){
示例:
AboutDialog