jsTree的使用文档还是不够详细。这里,根据项目需要,自己实作了基于jsTree的显示源代码结构的树:
核心代码如下:
- <script type="text/javascript" class="source below">
- var only_show_checked_except = false;
- var show_called_function = false;
- var show_interface_and_method;
- jQuery(document).ready(function(){
- // 初始化jsTree
- jQuery("#src_structure_tree")
- .bind("before.jstree", function (e, data) {
- })
- .bind("after_open.jstree", function(event, data){
- })
- .jstree({
- // List of active plugins
- // 注意:需要把"dnd","crrm","cookies","contextmenu"插件去掉!
- "plugins" : [
- "themes","json_data","checkbox","ui","contextmenu","search","types","hotkeys"
- ],
- "contextmenu": {
- "items": {
- "create": null,
- "rename": null,
- "remove": null,
- "ccp": null,
- "expand": {
- "label": "展开",
- "action": function (obj) {
- //jQuery('#src_structure_tree').jstree('open_all');
- jQuery('#src_structure_tree').jstree('open_all', obj);
- }
- }
- }
- },
- // I usually configure the plugin that handles the data first
- // This example uses JSON as it is most common
- "json_data" : {
- // This tree is ajax enabled - as this is most common, and maybe a bit more complex
- // All the options are almost the same as jQuery's AJAX (read the docs)
- "ajax" : {
- // the URL to fetch the data
- "url" : "getSrcStructure.action?appProfileId=${fiBean.appProfile.id}",
- // the `data` function is executed in the instance's scope
- // the parameter is the node being loaded
- // (may be -1, 0, or undefined when loading the root nodes)
- "data" : function (n) {
- // the result is fed to the AJAX request `data` option
- return {
- "operation" : "get_children",
- "id" : n.attr ? n.attr("id") : 0,
- "onlyShowCheckedExcept" : only_show_checked_except,
- "showCalledFunction" : show_called_function,
- "showInterfaceAndMethod" : show_interface_and_method
- };
- }
- }
- },
- "search" : {
- "show_only_matches" : true,
- "case_insensitive" : true,
- "ajax" : {
- // the URL to fetch the data
- "url" : "getSrcStructure.action?appProfileId=${fiBean.appProfile.id}",
- // the `data` function is executed in the instance's scope
- // the parameter is the node being loaded
- // (may be -1, 0, or undefined when loading the root nodes)
- "data" : function (n) {
- // the result is fed to the AJAX request `data` option
- return {
- "operation" : "get_children",
- "id" : n.attr ? n.attr("id") : 0,
- "onlyShowCheckedExcept" : only_show_checked_except,
- "showCalledFunction" : show_called_function,
- "showInterfaceAndMethod" : show_interface_and_method
- };
- }
- }
- },
- 'types': {
- // I set both options to -2, as I do not need depth and children count checking
- // Those two checks may slow jstree a lot, so use only when needed
- "max_depth" : -2,
- "max_children" : -2,
- // 这里更换图标
- 'types': {
- 'pkg' : {
- 'icon' : {
- 'image' : '<%=rootPath%>/common/jstree/themes/tcc/package_obj.png'
- }
- },
- 'cls' : {
- 'icon' : {
- 'image' : '<%=rootPath%>/common/jstree/themes/tcc/class_obj.png'
- }
- },
- 'method' : {
- 'icon' : {
- 'image' : '<%=rootPath%>/common/jstree/themes/tcc/methpub_obj.png'
- }
- },
- 'called_method' : {
- 'icon' : {
- 'image' : '<%=rootPath%>/common/jstree/themes/tcc/called_method.png'
- }
- }
- }
- }
- });
- // 选择改变事件,此API,文档上基本上只字未提!幸好在一个英文网站上看到解决方案
- jQuery("#src_structure_tree").bind("change_state.jstree", function (e, d) {
- getSelected();
- });
- });
- var target_pkgs = [];
- // 带上包名和类名,用'#'连接,注意,不可用美元符
- var target_clss = [];
- // 类名和方法名,用'@'连接
- var target_methods = [];
- var target_called_methods = [];
- // 判断sub是否是str的子串(注意,并非是真子串),不过,这里的情形,刚好是真子串
- function strContains(str, sub){
- return (str.indexOf(sub) != -1);
- }
- function existParentPackage(pkgArr, pkgName) {
- var exist = false;
- jQuery.each(pkgArr, function(key, val) {
- //回调函数有两个参数,第一个是元素索引,第二个为当前值
- if (strContains(pkgName, val))
- exist = true;
- });
- return exist;
- }
- function strArrToStr(strArr) {
- var result = "";
- var first = true;
- jQuery.each(strArr, function(key, val) {
- //回调函数有两个参数,第一个是元素索引,第二个为当前值(过滤两个特殊函数<init>和<clinit>
- if (first == true) {
- first = false;
- result += ' ' + val.replace(/</g,'<').replace(/>/g,'>').replace('#', '包中的').replace('@', '类中的方法').replace('!', '函数中所调用的').replace('@', '类中的方法');
- } else {
- result += ",<br/>" + ' ' + val.replace(/</g,'<').replace(/>/g,'>').replace('#', '包中的').replace('@', '类中的方法').replace('!', '函数中所调用的').replace('@', '类中的方法');
- }
- });
- return result;
- }
- function strArrToStr2(strArr) {
- var result = "";
- var first = true;
- jQuery.each(strArr, function(key, val) {
- //回调函数有两个参数,第一个是元素索引,第二个为当前值
- //注意:这里要用百分号间隔,不可以用逗号或分号,因为逗号和分号均会出现在方法签名中
- if (first == true) {
- first = false;
- result += val;
- } else {
- result += "%" + val;
- }
- });
- return result;
- }
- function getSelected() {
- // 重新初始化为空
- target_pkgs = [];
- target_clss = [];
- target_methods = [];
- target_called_methods = [];
- // 对选中的元素进行处理
- jQuery("#src_structure_tree").jstree("get_checked",null,true).each(function(i,n){
- var nodeType = jQuery(n).attr("rel");
- var pkg = jQuery(n).attr("pkg");
- var cls = jQuery(n).attr("cls");
- var method = jQuery(n).attr("method");
- var called_cls = jQuery(n).attr("called_cls");
- var called_method = jQuery(n).attr("called_method");
- if (nodeType == 'pkg') {
- if (!existParentPackage(target_pkgs, pkg)) {
- target_pkgs.push(pkg);
- return;
- }
- }
- if (nodeType == 'cls') {
- //alert("cls:" + cls);
- // 如果所在包已经完全被选择,返回
- if (existParentPackage(target_pkgs, pkg)) {
- return;
- }
- // 不做去重处理,因为实际上这种情形不可能出现,除非是服务端程序逻辑有问题
- target_clss.push(pkg + '#' + cls);
- return;
- }
- if (nodeType == 'method') {
- // 如果所在包已经完全被选择,返回
- if (existParentPackage(target_pkgs, pkg)) {
- return;
- }
- // 如果所在类已经完全被选择,返回
- if (existParentPackage(target_clss, pkg + '#' + cls)) {
- return;
- }
- // 不做去重处理,因为实际上这种情形不可能出现,除非是服务端程序逻辑有问题
- target_methods.push(pkg + '#' + cls + '@' + method);
- }
- if (nodeType == 'called_method') {
- target_called_methods.push(pkg + '#' + cls + '@' + method + '!' + called_cls + '@' + called_method);
- }
- });
- var target_pkgs_value = strArrToStr(target_pkgs);
- var target_clss_value = strArrToStr(target_clss);
- var target_methods_value = strArrToStr(target_methods);
- var target_called_methods_value = strArrToStr(target_called_methods);
- jQuery("#target_pkgs").html(target_pkgs_value);
- jQuery("#target_clss").html(target_clss_value);
- jQuery("#target_methods").html(target_methods_value);
- jQuery("#target_called_methods").html(target_called_methods_value);
- jQuery("#hidden_target_pkgs").val(strArrToStr2(target_pkgs));
- jQuery("#hidden_target_clss").val(strArrToStr2(target_clss));
- jQuery("#hidden_target_methods").val(strArrToStr2(target_methods));
- jQuery("#hidden_target_called_methods").val(strArrToStr2(target_called_methods));
- if (target_pkgs_value != "") {
- jQuery("#target_pkgs_result").html(" <b>" + target_pkgs_value + "</b>");
- } else {
- jQuery("#target_pkgs_result").html(" <b>空</b>");
- }
- if (target_clss_value != "") {
- jQuery("#target_clss_result").html(" <b>" + target_clss_value + "</b>");
- } else {
- jQuery("#target_clss_result").html(" <b>空</b>");
- }
- if (target_methods_value != "") {
- jQuery("#target_methods_result").html(" <b>" + target_methods_value + "</b>");
- } else {
- jQuery("#target_methods_result").html(" <b>空</b>");
- }
- if (target_called_methods_value != "") {
- jQuery("#target_called_methods_result").html(" <b>" + target_called_methods_value + "</b>");
- } else {
- jQuery("#target_called_methods_result").html(" <b>空</b>");
- }
- validateStep1();
- }
- </script>
关键是要理解代码中这句英文注解的意思:
// the result is fed to the AJAX request `data` option
也就是说,在紧接着的return块的内容(多个键值对),会被post给struts2的action。
显然,第一次请求发过去的id为0。
相关的服务端处理代码:
- public class SrcStructureAction extends BaseAction {
- private static final long serialVersionUID = 1L;
- private String appProfileId;
- private String id;
- // 是否只显示受检查异常
- private String onlyShowCheckedExcept;
- // 是否显示函数中被调用的方法
- private String showCalledFunction;
- // 通过接口和方法名进行过滤
- private String showInterfaceAndMethod;
- private FaultInjectService faultInjectService;
- public SrcStructureAction() {
- }
- public String getSrcStructure() {
- if (showInterfaceAndMethod == null) {
- showInterfaceAndMethod = "";
- } else {
- showInterfaceAndMethod = showInterfaceAndMethod.trim().replace(".", "/");
- }
- String result = faultInjectService.getSrcStructureStr(appProfileId, id, onlyShowCheckedExcept, showCalledFunction, showInterfaceAndMethod);
- writeToClient(result);
- return null;
- }
writeToClient:
- protected void writeToClient(String content) {
- try {
- PrintWriter out = getResponseWriter();
- out.print(content);
- out.flush();
- } catch (IOException e) {
- //throw new ServiceException(e.getMessage(),e);
- }
- }
getSrcStructureStr的使命就是返回jsTree可以正确识别的json格式的字符串。根据不同节点的id的值,返回不同的孩子节点信息。
当查询条件改变时,只要更改条件,然后重新刷新树即可,这时jsTree就会重新发起请求,重新构造一棵新的树。
- jQuery('#src_structure_tree').jstree('refresh',-1);
接下来,展示代码,因为代码太长,分开到下篇阐述。