【原创】JQWidgets-TreeGrid 2、初探源码

 

已知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渲染器、如果想要修改控件展示逻辑,则应该深入其代码进行研究。

可能目前的需求只需要该某行代码,而我们只关注这些代码,不去扩展,当以后需求变更或增加时,还得重新回过头来继续研究这些代码的扩展部分,对我们来说,迟早的事情理应要一次性做到位,切忌得过且过

 

posted @ 2017-05-16 17:50  侍诚  阅读(662)  评论(0编辑  收藏  举报