已知JQWidgets的TreeGrid组件依赖于jqxcore.js、jqxtreegrid.js,实际上它还依赖于jqxdatatable.js。我们先通过一个例子,来探索本次的话题。
需求:
图1
如图,我们有个表格,它具有【收起-展开】的功能,图中标红的部分是JQWidgets的expand-button模型。
目前默认是第一列,根据系统的实际需求,处理人可以串联邀请多个其他的处理人进行审批工作,那我们在展示时,应该针对审批结果来渲染【收起-展开】的按钮(用户是先关注结果,结果是串联,然后再点击展开,查看哪些人被邀请)。
但翻遍了官方API,也没有看到有接口可以改变expand-button所在的列,最后跟踪源码,发现了一些端倪。
解决方案探索:
我们先来跟踪初始化函数、看一下TreeGrid的大致执行流程。
1、当我们调用 $("#treeGrid").jqxTreeGrid(iniObj)时,他首先进入到jqxcore.js中,分析断点行处的a.jqx.applyWidget函数,顾名思义,类似于js的apply函数,其中参数c为我们的treeGrid的HTMLElement,参数f为"jqxTreeGrid"字符串,参数b就是我们初始化时的赋予的对象(iniObj)。由此我们可以知道TreeGrid初始化时,是先经过jqxcore.js进行中转,jqxcore.js的a.fn[f]类似一个中转器。
备注:如果没有特殊的配置,一般是调用applyWidget方法,断点处d=a.data(c,f),a实际上就是JQuery,根据笔者测试 $.data(name,value) 似乎默认都是返回undefined。
图2
2、控制权到了applyWidget的具体实现,他有一个关键的调用e.createInstanced(d)。
d是我们传入的参数对象iniObj
e是在上面有一段代码进行初始化(LIne:3360)
e ? (e.host = g, e.element = b) : e = new a.jqx["_" + c],"" == b.id && (b.id = a.jqx.utilities.createId())
由第一步可知,e根本没有传入,所以此处e实际上就等于new a.jqx["_"+c];//c=jqxTreeGrid。
到此,我们总结一下前面的步骤,当我们在调用$("#treeGrid").jqxTreeGrid(iniObj)时,代码会走到 a.jqx.applyWidge方法中,而jqxTreeGrid方法最终会作为一个字符串参数传入到a.jqx.applyWidget。
而后,我们分析applyWidget的代码实现,我们可以简单认为,$("#treeGrid").jqxTreeGrid(iniObj) 实际上就是 a.jqx["_jqxTreeGrid"].createInstance(iniObj)。
图3
3、当我们进入createInstanced方法时,会发现,代码居然进入到了神秘的jqxdatatable.js(图4),而后再次走下去才进入到jqxtreegrid.js(图5)。
图4
图5
实际上,在第二步时,我们忽略了一个关键代码:代码其实是在循环中调用e.createInstance时,而循环的主体是变量i,往上看,i的赋值代码如下:
for (var i = new Array, e = h.instance; e;)
e.isInitialized = !1,
i.push(e),
e = e.base;
我们发现i的值实际上是push进去的,所以我们可以得出结论:jqxTreeGrid对象的base属性是一个jqxdatatable对象!!
我们打开jqxtreegrid.js,目光锁定第8行,jqxtreegrid.js果然和jqxDataTable有关系,再根据base属性的意思,我们猜想这个JqxTreeGrid是基于JqxDataTable实现的(得出这个结论似乎没什么卵用 ((╯' - ')╯ ┻━┻ )
好了,我们回到关键问题,目前我们大致了解数据流向以及总体结构,我们的问题是想要修改expand-button所在的列,看着似乎比较远,但实际上以及差不多摸到真相了。
图6
4、总体浏览一下jqxtreegrid.js文件,发现文件并不大,不到一千行代码,该文件主要声明了一些TreeGrid特有的方法和关键的_renderrows方法,众所周知,一般框架都会存在一个rederer渲染器,渲染器一般是根据对象内部数据进行html元素的描绘,因此,我们聚焦这个_renderrows方法。
在jqxTreeGrid中,如果想要拥有【收起-展开】的功能,则需要在dataAdapter中定义hierarchy属性(dataAdapter的例子可以参考笔者的另外一篇文章),所以,我们现在_renderrows方法中搜索hierarchy这关键字。
经过笔者煞费苦心的跟踪、分析代码,最后定位到jqxtreegrid.js中Line:300-Line:447行就是 画表格的关键代码,且我们一直寻找的expand-button也在其中。先附上jqxtreegrid.js中Line:300-Line:447行的代码
for (var K = b.source._source.hierarchy && b.source._source.hierarchy.groupingDataFields ? b.source.s_ource.hierarchy.groupingDataFields.length : 0, L = 0; L < j.length; L++) {
var M = j[L],
N = M.uid;
K > 0 && M[d.level] < K && (N = M.uid),
void 0 === M.uid && (M.uid = b.dataview.generatekey());
var F = '<tr data-key="' + N + '" role="row" id="row' + L + b.element.id + '">',
O = '<tr data-key="' + N + '" role="row" id="row' + L + b.element.id + '">';
if (M.aggregate) var F = '<tr data-role="summaryrow" role="row" id="row' + L + b.element.id + '">',
O = '<tr data-role="summaryrow" role="row" id="row' + L + b.element.id + '">';
var P = 0;
if (b.rowinfo[N]) void 0 === b.rowinfo[N].checked && (b.rowinfo[N].checked = M[d.checked]),
void 0 === b.rowinfo[N].icon && (b.rowinfo[N].icon = M[d.icon]),
void 0 === b.rowinfo[N].aggregate && (b.rowinfo[N].aggregate = M[d.aggregate]),
void 0 === b.rowinfo[N].row && (b.rowinfo[N].row = M),
void 0 === b.rowinfo[N].leaf && (b.rowinfo[N].leaf = M[d.leaf]),
void 0 === b.rowinfo[N].expanded && (b.rowinfo[N].expanded = M[d.expanded]);
else {
var Q = M[d.checked];
void 0 === Q && (Q = !1),
b.rowinfo[N] = {
selected: M[d.selected],
checked: Q,
icon: M[d.icon],
aggregate: M.aggregate,
row: M,
leaf: M[d.leaf],
expanded: M[d.expanded]
}
}
var R = b.rowinfo[N];
R.row = M,
M.originalRecord && (R.originalRecord = M.originalRecord);
for (var S = 0, u = 0; u < h; u++) {
var T = b.columns.records[u];
(T.pinned || b.rtl && b.columns.records[h - 1].pinned) && (E = !0);
var w = T.width;
w < T.minwidth && (w = T.minwidth),
w > T.maxwidth && (w = T.maxwidth),
w -= s,
w < 0 && (w = 0);
var g = b.toTP("jqx-cell") + " " + b.toTP("jqx-grid-cell") + " " + b.toTP("jqx-item");
T.pinned && (g += " " + b.toTP("jqx-grid-cell-pinned")),
b.sortcolumn === T.displayfield && (g += " " + b.toTP("jqx-grid-cell-sort")),
b.altRows && L % 2 != 0 && (g += " " + b.toTP("jqx-grid-cell-alt")),
b.rtl && (g += " " + b.toTP("jqx-cell-rtl"));
var U = "";
if (K > 0 && !i && !M.aggregate && M[d.level] < K) {
U += ' colspan="' + h + '"';
for (var D = 0, V = 0; V < h; V++) {
var W = b.columns.records[V];
if (!W.hidden) {
var X = W.width;
X < W.minwidth && (w = W.minwidth),
X > W.maxwidth && (w = W.maxwidth),
X -= s,
X < 0 && (X = 0),
D += X
}
}
w = D
}
var x = '<td role="gridcell"' + U + ' style="max-width:' + w + "px; width:" + w + "px;",
Y = '<td role="gridcell"' + U + ' style="pointer-events: none; visibility: hidden; border-color: transparent; max-width:' + w + "px; width:" + w + "px;";
u == h - 1 && 1 == h && (x += "border-right-color: transparent;", Y += "border-right-color: transparent;"),
K > 0 && M[d.level] < K && !M.aggregate ? b.rtl && (g += " " + b.toTP("jqx-right-align")) : "left" != T.cellsalign && (g += "right" === T.cellsalign ? " " + b.toTP("jqx-right-align") : " " + b.toTP("jqx-center-align")),
R && (R.selected && b.editKey !== N && "none" !== b.selectionMode && (g += " " + b.toTP("jqx-grid-cell-selected"), g += " " + b.toTP("jqx-fill-state-pressed")), R.locked && (g += " " + b.toTP("jqx-grid-cell-locked")), R.aggregate && (g += " " + b.toTP("jqx-grid-cell-pinned"))),
T.hidden ? (x += "display: none;", Y += "display: none;", b._hiddencolumns = !0) : (0 != S || b.rtl ? (x += "border-right-width: 0px;", Y += "border-right-width: 0px;") : (x += "border-left-width: 0px;", Y += "border-left-width: 0px;"), S++, P += s + w),
T.pinned && (x += "pointer-events: auto;", Y += "pointer-events: auto;");
var Z = "";
if (0 != b.source.hierarchy.length && M.records && (!M.records || 0 !== M.records.length) || this.virtualModeCreateRecords || (R.leaf = !0), M.records && M.records.length > 0 && (R.leaf = !1), b.dataview.filters.length > 0 && M.records && M.records.length > 0) {
for (var $ = !1, _ = 0; _ < M.records.length; _++) if (M.records[_]._visible !== !1 && void 0 == M.records[_].aggregate) {
$ = !0;
break
}
$ ? R.leaf = !1 : R.leaf = !0
}
R && !R.leaf && (R.expanded ? (Z += b.toTP("jqx-tree-grid-expand-button") + " ", Z += b.rtl ? b.toTP("jqx-grid-group-expand-rtl") : b.toTP("jqx-grid-group-expand"), Z += " " + b.toTP("jqx-icon-arrow-down")) : (Z += b.toTP("jqx-tree-grid-collapse-button") + " ", b.rtl ? (Z += b.toTP("jqx-grid-group-collapse-rtl"), Z += " " + b.toTP("jqx-icon-arrow-left")) : (Z += b.toTP("jqx-grid-group-collapse"), Z += " " + b.toTP("jqx-icon-arrow-right")))),
(!b.autoRowHeight || 1 === S || b.autoRowHeight && !T.autoCellHeight) && (g += " " + b.toTP("jqx-grid-cell-nowrap"));
var aa = b._getcellvalue(T, R.row);
if (K > 0 && !M.aggregate && M[d.level] < K && (aa = M.label), "" != T.cellsFormat && a.jqx.dataFormat && (a.jqx.dataFormat.isDate(aa) ? aa = a.jqx.dataFormat.formatdate(aa, T.cellsFormat, b.gridlocalization) : (a.jqx.dataFormat.isNumber(aa) || !isNaN(parseFloat(aa)) && isFinite(aa)) && (aa = a.jqx.dataFormat.formatnumber(aa, T.cellsFormat, b.gridlocalization))), "" != T.cellclassname && T.cellclassname) if ("string" == typeof T.cellclassname) g += " " + T.cellclassname;
else {
var ba = T.cellclassname(L, T.datafield, b._getcellvalue(T, R.row), R.row, aa);
ba && (g += " " + ba)
}
if ("" != T.cellsRenderer && T.cellsRenderer) {
var ca = T.cellsRenderer(N, T.datafield, b._getcellvalue(T, R.row), R.row, aa);
void 0 !== ca && (aa = ca)
}
if (R.aggregate && T.aggregates) {
var da = M.siblings.slice(0, M.siblings.length - 1),
ea = b._calculateaggregate(T, null, !0, da);
if (M[T.displayfield] = "", ea) if (T.aggregatesRenderer) {
if (ea) {
var fa = T.aggregatesRenderer(ea[T.datafield], T, null, b.getcolumnaggregateddata(T.datafield, T.aggregates, !1, da), "subAggregates");
aa = fa,
M[T.displayfield] += name + ":" + ea[T.datafield] + "\n"
}
} else aa = "",
M[T.displayfield] = "",
a.each(ea, function () {
var a = this;
for (obj in a) {
var c = obj;
c = b._getaggregatename(c);
var d = '<div style="position: relative; margin: 0px; overflow: hidden;">' + c + ":" + a[obj] + "</div>";
aa += d,
M[T.displayfield] += c + ":" + a[obj] + "\n"
}
});
else aa = ""
}
if (1 === S && !b.rtl || T == C && b.rtl || K > 0 && M[d.level] < K) {
for (var ga = "", ha = b.toThemeProperty("jqx-tree-grid-indent"), ia = R.leaf ? 1 : 0, ja = 0; ja < M[d.level] + ia; ja++) ga += "<span class='" + ha + "'></span>";
var ka = "<span class='" + Z + "'></span>",
la = "",
ma = "";
if (this.checkboxes && !M.aggregate) {
var na = b.toThemeProperty("jqx-tree-grid-checkbox") + " " + ha + " " + b.toThemeProperty("jqx-checkbox-default") + " " + b.toThemeProperty("jqx-fill-state-normal") + " " + b.toThemeProperty("jqx-rc-all"),
oa = !0;
if (a.isFunction(this.checkboxes) && (oa = this.checkboxes(N, M), void 0 == oa && (oa = !1)), oa) if (R) {
var pa = R.checked;
0 == this.hierarchicalCheckboxes && null === pa && (pa = !1),
la += pa ? "<span class='" + na + "'><div class='" + b.toThemeProperty("jqx-tree-grid-checkbox-tick") + " " + b.toThemeProperty("jqx-checkbox-check-checked") + "'></div></span>" : pa === !1 ? "<span class='" + na + "'></span>" : "<span class='" + na + "'><div class='" + b.toThemeProperty("jqx-tree-grid-checkbox-tick") + " " + b.toThemeProperty("jqx-checkbox-check-indeterminate") + "'></div></span>"
} else la += "<span class='" + na + "'></span>"
}
if (this.icons && !M.aggregate) {
var qa = b.toThemeProperty("jqx-tree-grid-icon") + " " + ha;
if (b.rtl) var qa = b.toThemeProperty("jqx-tree-grid-icon") + " " + b.toThemeProperty("jqx-tree-grid-icon-rtl") + " " + ha;
var ra = b.toThemeProperty("jqx-tree-grid-icon-size") + " " + ha,
sa = R.icon;
a.isFunction(this.icons) && (R.icon = this.icons(N, M), R.icon && (sa = !0)),
sa && (ma += R.icon ? "<span class='" + qa + "'><img class='" + ra + "' src='" + R.icon + "'/></span>" : "<span class='" + qa + "'></span>")
}
var ta = b.autoRowHeight && 1 === S && T.autoCellHeight ? " " + b.toTP("jqx-grid-cell-wrap") : "",
ua = ga + ka + la + ma + "<span class='" + b.toThemeProperty("jqx-tree-grid-title") + ta + "'>" + aa + "</span>";
aa = b.rtl ? "<span class='" + b.toThemeProperty("jqx-tree-grid-title") + ta + "'>" + aa + "</span>" + ma + la + ka + ga : ua
}
if (K > 0 && i && u >= K && M[d.level] < K && (x += "padding-left: 5px; border-left-width: 0px;", Y += "padding-left: 5px; border-left-width: 0px;", aa = "<span style='visibility: hidden;'>-</span>"), x += '" class="' + g + '">', x += aa, x += "</td>", Y += '" class="' + g + '">', Y += aa, Y += "</td>", T.pinned ? (O += x, F += x) : (F += x, E && (O += Y)), K > 0 && !i && M[d.level] < K && !M.aggregate) break
}
if (0 == f && (b.table[0].style.width = P + 2 + "px", f = P), F += "</tr>", O += "</tr>", A += F, B += O, b.rowDetails && !M.aggregate && this.rowDetailsRenderer) {
var va = '<tr data-role="row-details"><td valign="top" align="left" style="pointer-events: auto; max-width:' + w + "px; width:" + w + 'px; overflow: hidden; border-left: none; border-right: none;" colspan="' + b.columns.records.length + '" role="gridcell"',
g = b.toTP("jqx-cell") + " " + b.toTP("jqx-grid-cell") + " " + b.toTP("jqx-item");
g += " " + b.toTP("jqx-details"),
g += " " + b.toTP("jqx-reset");
var wa = this.rowDetailsRenderer(N, M);
wa && (va += '" class="' + g + '"><div style="pointer-events: auto; overflow: hidden;"><div data-role="details">' + wa + "</div></div></td></tr>", A += va, B += va)
}
}
根据笔者层层努力,最终跟踪到代码块中的第112行: 1===S。改成4===S后,刷新页面,果然生效。
图7
但是随之问题又出来了,框架将这个写死了,我们不能将这个写死,因此可以考虑改为可配置方式,此时我们有两个选择,一是修改jqxdatatable.js、二是修改jqxtreegrid.js,考虑到_renderrows属于jqxtreegrid.js,那最终决定在jqxtreegrid.js添加我们的代码:
图8
图9
图10
调用代码
$("#treeGrid").jqxTreeGrid(
{
source: dataAdapter,
hierarchyIconColumnField:'Fresult',
columns: [
{ text: 'id', dataField: 'Fid', width: 140 ,hidden:true},
{ text: '序号', dataField: 'order', width: 50 ,align: 'center'},
{ text: '处理人', dataField: 'Foperator', width: 180 ,align: 'center',cellsAlign: 'left',cellsRenderer:
function (row, column, value){
return '****';
}},
{ text: '所属环节', dataField: 'Fdepartment', width: 100 ,align: 'center',cellsAlign: 'center'},
{ text: '审批结果', dataField: 'Fresult', width: 180, align: 'center', cellsAlign: 'center' },
{ text: '上传日期', dataField: 'Ftime', width: 160, align: 'center', cellsAlign: 'center',cellsFormat: "yyyy-MM-dd HH:mm:ss" },
{ text: '备注意见', dataField: 'Fopinion', width: 500, align: 'center', cellsAlign: 'center' ,cellsRenderer:
function (row, column, value){
return '****';
}},
{ text: '附件名称', dataField: 'Foption_name', width: 300, align: 'center', cellsAlign: 'center' },
{ text: '附件类型', dataField: 'Foption_type', width: 160, align: 'center', cellsAlign: 'center'}
],
ready:function(){
$("#treeGrid").jqxTreeGrid('expandAll');
},
theme:'light',
columnsResize:true
});
最终效果:
图11
总结:
JQWidgets实际上仍然有很多不足的地方,但当我们摸清其代码的规律,自己动手去修改其代码且完成达到目的,是一件颇具满足感的事情。
本文主要是根据一个小需求来探索TreeGrid组件、所有的JQWdigets都根据jqxcore.js的applyWidget去分发事件,如果是初始化对象时,则会调用对应组件的createInstance方法。每个JQWdigets组件都有renderer渲染器、如果想要修改控件展示逻辑,则应该深入其代码进行研究。
可能目前的需求只需要该某行代码,而我们只关注这些代码,不去扩展,当以后需求变更或增加时,还得重新回过头来继续研究这些代码的扩展部分,对我们来说,迟早的事情理应要一次性做到位,切忌得过且过