利用Ajax实现树的动态加载
来源:互联网 发布:迅捷流程图制作软件 编辑:程序博客网 时间:2024/04/28 00:19
在很多项目中我们都会用到树,或者用类似的TreeView控件,或者直接用JavaScript和HTML来直接实现。一般情况下,我们都是一次性将所有的节点都加载到树上,当树的节点不是很多的情况下倒没什么问题,但是如果树的节点数很多的时候就影响显示效率了。
去年曾经在一个项目里用树结构来显示一个单位的岗位,在实际环境中岗位数多达两三千个,利用传统的方法一次性将所有岗位都加载到树上时,用户打开这个页面到显示完这个树控件需要等待很长时间(长达几分钟),严重影响了UT体验。于是我就开始查找在哪里出了问题,为什么这么个树需要等待这么久时间才能显示出来呢?
首先我怀疑是从数据库里一次性取出所有树节点数据消耗了很多时间,但经过仔细分析和测试,从数据库取几千条数据是很正常的事情,所用的时间非常少,这不是影响树显示的主要原因。而且将这些数据组织成一个有序数组也是很快就完成了。然后猜想是不是这些节点生成的HTML代码太多,所以网络传输耗了太多时间? 但经过测试后这个原因也被排除了。最后才确定了主要原因在于在客户端将所有这些节点组织成一棵完整的树显示出来消耗了大部分时间。(在数据量特别大的情况下,前两种分析是会影响树的效率的)
为了解决这个问题,我就尝试动态加载树节点,即在初始化时只加载少数节点(如只加载根节点或加载第一级节点),然后当用户点击展开某个节点时再动态加载该节点的子节点,如图(下载完整示例代码):
实际原理非常简单,树的组织利用JavaScript(Tigra Tree)来实现,动态加载节点利用了Ajax技术。在此将大致思路记录一下,但没有对整个Tree做进一步的封装。
(一) 负责组织树的JavaScript代码( NodeDyn.js )
- onefolder = false
- norootclose = true
- norootselect = true
- closesubs = false
- function tree (a_items, a_template, f_getchild,treeContainerId )
- {
- this.a_tpl = a_template;
- this.a_config = a_items;
- this.o_root = this;
- this.a_index = [];
- this.o_selected = null;
- this.n_depth = -1;
- this.f_getchild = f_getchild; //取下级结点的函数
- var o_icone = new Image(),
- o_iconl = new Image();
- o_icone.src = a_template['icon_e'];
- o_iconl.src = a_template['icon_l'];
- a_template['im_e'] = o_icone;
- a_template['im_l'] = o_iconl;
- for (var i = 0; i < 64; i++)
- if (a_template['icon_' + i]) {
- var o_icon = new Image();
- a_template['im_' + i] = o_icon;
- o_icon.src = a_template['icon_' + i];
- }
- //add by ghqian 2007-10-12
- this.toggleByItem = tree_toggleByItem;
- this.toggleByData = tree_toggleByData; //只能展开已经生成的节点,不处理待动态生成的节点
- this.selectByData = tree_selectByData; //展开到指定节点(不处理待动态生成的节点),选择该节点
- this.toggle = function (n_id)
- {
- var o_item = this.a_index[n_id];
- this.toggleByItem( o_item , false )
- };
- this.select = function (n_id)
- {
- return this.a_index[n_id].select();
- };
- this.mout = function (n_id)
- {
- this.a_index[n_id].upstatus(true)
- };
- this.mover = function (n_id)
- {
- this.a_index[n_id].upstatus()
- };
- this.a_children = [];
- for (var i = 0; i < a_items.length; i++)
- new tree_item(this, i);
- this.n_id = trees.length;
- trees[this.n_id] = this;
- //HTML Container add by ghqian 2007-10-24
- var container = null;
- if( treeContainerId )
- container = document.getElementById(treeContainerId);
- if( container)
- container.innerHTML = "";
- for (var i = 0; i < this.a_children.length; i++)
- {
- var mHtml = this.a_children[i].init();
- if( container )
- container.innerHTML += mHtml;
- else
- document.write( mHtml );
- this.a_children[i].open();
- }
- }
- ////////////// toggle & select by data Add by ghqian 2007-10-12///////////////////
- //展开到指定节点(不处理待动态生成的节点),选择该节点
- //如果runEvent为true,同时出发对应事件
- function tree_selectByData( m_data, runEvent )
- {
- for( var i=0;i<this.a_index.length;i++)
- {
- var o_item = this.a_index[i];
- if( o_item.m_data==m_data )
- {
- var parentItem = o_item.o_parent;
- if( parentItem != null )
- this.toggleByItem(parentItem ,true);
- //this.toggleByItem(o_item,true);
- o_item.select();
- if( runEvent )
- eval( o_item.a_config[1] );
- return;
- }
- }
- }
- function tree_toggleByItem( o_item, toggleParent )
- {
- if( o_item && o_item.n_depth>-1)
- {
- if( toggleParent )
- {
- var parentItem = o_item.o_parent;
- if( parentItem && parentItem.n_depth>-1 && !parentItem.b_opened )
- this.toggleByItem( parentItem, toggleParent );
- }
- if( o_item && !o_item.b_opened)
- {
- if (onefolder)
- expand_or_collapse_level(this,o_item.n_depth)
- };
- o_item.open(o_item.b_opened)
- }
- }
- //根据m_data 展开 Add by ghqian 2007-10-11
- function tree_toggleByData( m_data )
- {
- for( var i=0;i<this.a_index.length;i++)
- {
- var o_item = this.a_index[i];
- if( o_item.m_data==m_data )
- {
- this.toggleByItem(o_item,true);
- return;
- }
- }
- }
- /////////////////////////////////////////
- function tree_item (o_parent, n_order) {
- this.n_depth = o_parent.n_depth + 1;
- this.a_config = o_parent.a_config[n_order + (this.n_depth ? 4 : 0)];
- if (!this.a_config) return;
- this.o_root = o_parent.o_root;
- this.o_parent = o_parent;
- this.n_order = n_order;
- this.b_opened = !this.n_depth;
- this.b_havechild = this.a_config[2]; //add by ghqian
- this.m_data = this.a_config[3]; //add by ghqian
- this.n_id = this.o_root.a_index.length;
- this.o_root.a_index[this.n_id] = this;
- o_parent.a_children[n_order] = this;
- this.a_children = [];
- for (var i = 0; i < this.a_config.length - 4; i++)
- new tree_item(this, i);
- this.get_icon = item_get_icon;
- this.open = item_open;
- this.select = item_select;
- this.init = item_init;
- this.upstatus = item_upstatus;
- this.is_last = function ()
- {
- return this.n_order == this.o_parent.a_children.length - 1
- };
- }
- //将item的子结点生成HTML联合起来放到o_idiv里 add by ghqian 2007-10-11
- function item_open_joinChildHtml(o_idiv,item)
- {
- var a_children = [];
- for (var i = 0; i < item.a_children.length; i++)
- a_children[i]= item.a_children[i].init();
- o_idiv.innerHTML = a_children.join('');
- }
- //处理item_open的善后问题 add by ghqian 2007-10-11
- function item_open_tail(o_idiv,item,b_close)
- {
- var o_jicon = document.images['j_img' + item.o_root.n_id + '_' + item.n_id],
- o_iicon = document.images['i_img' + item.o_root.n_id + '_' + item.n_id];
- //判断是否取到了下级数据,取不到下级数据时做如下处理
- if( o_idiv.innerHTML=="" )
- {
- //移除DIV
- if( o_idiv.parentNode )
- o_idiv.parentNode.removeChild( o_idiv );
- else
- o_idiv.style.display="none";
- //替换加减号等图片
- item.b_opened = false;
- item.b_havechild = false;
- if (o_jicon) o_jicon.src = item.get_icon(true);
- //移出<a>元素的事件
- var po = o_jicon.parentNode;
- if( po )
- {
- po.outerHTML = po.innerHTML;
- }
- }
- else
- {
- item.b_opened = !b_close;
- if (o_jicon) o_jicon.src = item.get_icon(true);
- }
- if (o_iicon) o_iicon.src = item.get_icon();
- item.upstatus();
- }
- //添加临时节点,提示Loading... add by ghqian 2007-10-11
- function item_open_addTipItem(o_idiv,item)
- {
- item.a_config[item.a_config.length] = ["<strong>Loading...</strong>","",false];
- var tipItem = new tree_item(item,0);
- o_idiv.innerHTML = tipItem.init();
- item.a_config.splice( item.a_config.length-1,1 );
- item.a_children.splice( 0,item.a_children.length );
- }
- //动态添加下级节点 add by ghqian 2007-10-11
- function item_open_dynGetChild(o_idiv,item,b_close)
- {
- //取下级结点
- var childItem = item.o_root.f_getchild(item.m_data);
- if( childItem )
- {
- for( var m=0;m<childItem.length;m++)
- item.a_config[item.a_config.length] = childItem[m];
- }
- for (var i = 0; i < item.a_config.length - 4; i++)
- new tree_item(item, i);
- item_open_joinChildHtml(o_idiv,item);
- //善后
- item_open_tail(o_idiv,item,b_close)
- }
- //modify by ghqian 2007-10-11 修正Loading...
- function item_open (b_close)
- {
- if (!(this.n_depth==0 && b_close && norootclose))
- { //**** to avoid collapsing of first voice
- //**** Added to close subfolders ****
- if (!manageall && closesubs)
- {
- for (var i = 0; i < this.a_children.length; i++)
- this.a_children[i].open(true)
- }
- //***********************************
- var meItem = this; //add by ghqian 2007-10-11
- var o_idiv = get_element('i_div' + this.o_root.n_id + '_' + this.n_id);
- if (!o_idiv) return;
- o_idiv.style.display = (b_close ? 'none' : 'block');
- if (!o_idiv.innerHTML)
- {
- //如果没有下级结点,但属性显示有下级结点,说明需要动态加载下级结点
- if( !this.a_children.length && this.b_havechild )
- {
- //将提示信息结点加上去
- item_open_addTipItem(o_idiv,meItem);
- //取下级结点
- setTimeout( function(){ item_open_dynGetChild(o_idiv,meItem,b_close) } ,0)
- return;
- }
- item_open_joinChildHtml(o_idiv,meItem);
- }
- //善后
- item_open_tail(o_idiv,meItem,b_close)
- }
- }
- function item_select (b_deselect)
- {
- // if (!(this.n_depth==0 && norootselect)) { //**** to avoid selection of first voice
- if (!b_deselect)
- {
- var o_olditem = this.o_root.o_selected;
- this.o_root.o_selected = this;
- if (o_olditem) o_olditem.select(true);
- }
- var o_iicon = document.images['i_img' + this.o_root.n_id + '_' + this.n_id];
- if (o_iicon)
- o_iicon.src = this.get_icon();
- get_element('i_txt' + this.o_root.n_id + '_' + this.n_id).style.fontWeight = b_deselect ? 'normal' : 'bold';
- // }
- this.upstatus();
- return Boolean(this.a_config[1]);
- }
- function item_upstatus (b_clear)
- {
- //window.setTimeout('window.status="' + (b_clear ? '' : this.a_config[0] + (this.a_config[1] ? ' ('+ this.a_config[1] + ')' : '')) + '"', 10);
- }
- function item_init ()
- {
- var a_offset = [],
- o_current_item = this.o_parent;
- for (var i = this.n_depth; i > 1; i--)
- {
- a_offset[i] = '<img src="' + this.o_root.a_tpl[o_current_item.is_last() ? 'icon_e' : 'icon_l'] + '" border="0" align="absbottom">';
- o_current_item = o_current_item.o_parent;
- }
- return '<table cellpadding="0" cellspacing="0" border="0"><tr><td nowrap>' +
- (this.n_depth ?
- a_offset.join('') + (
- (this.a_children.length>0||this.b_havechild) ?
- '<a onfocus=this.blur() href="" onClick="trees[' + this.o_root.n_id + '].toggle(' + this.n_id + ');trees[' + this.o_root.n_id + '].select(' + this.n_id + ');return false" onmouseover="trees[' + this.o_root.n_id + '].mover(' + this.n_id + ')" onmouseout="trees[' + this.o_root.n_id + '].mout(' + this.n_id + ')"><img src="' + this.get_icon(true) + '" border="0" align="absbottom" name="j_img' + this.o_root.n_id + '_' + this.n_id + '"></a>'
- : '<img src="' + this.get_icon(true) + '" border="0" align="absbottom">')
- : '')
- + '<a href="#" onfocus=this.blur(); onclick=trees[' + this.o_root.n_id + '].select(' + this.n_id + ');'+this.a_config[1]+' ondblclick="trees[' + this.o_root.n_id + '].toggle(' + this.n_id + ');return trees[' + this.o_root.n_id + '].select(' + this.n_id + ')" onmouseover="trees[' + this.o_root.n_id + '].mover(' + this.n_id + ')" onmouseout="trees[' + this.o_root.n_id + '].mout(' + this.n_id + ')" class="t' + this.o_root.n_id + 'i" id="i_txt' + this.o_root.n_id + '_' + this.n_id + '"><img src="' + this.get_icon() + '" border="0" align="absbottom" name="i_img' + this.o_root.n_id + '_' + this.n_id + '" class="t' + this.o_root.n_id + 'im">' + this.a_config[0] + '</a></td></tr></table>' + ((this.a_children.length>0||this.b_havechild) ? '<div id="i_div' + this.o_root.n_id + '_' + this.n_id + '" style="display:none"></div>' : '');
- }
- function item_get_icon (b_junction)
- {
- return this.o_root.a_tpl['icon_' + ((this.n_depth ? 0 : 32) + ((this.a_children.length>0||this.b_havechild) ? 16 : 0) + ((this.a_children.length>0||this.b_havechild) && this.b_opened ? 8 : 0) + (!b_junction && this.o_root.o_selected == this ? 4 : 0) + (b_junction ? 2 : 0) + (b_junction && this.is_last() ? 1 : 0))];
- }
- var trees = [];
- get_element = document.all ?
- function (s_id) { return document.all[s_id] } :
- function (s_id) { return document.getElementById(s_id) };
- var manageall=false
- function expand_or_collapse_all (o_tree, b_collapse) {
- manageall=true
- for (var i = 1; i < o_tree.a_index.length; i++)
- {
- var o_item = o_tree.a_index[i];
- if( o_item.a_children.length>0 )
- o_item.open(b_collapse);
- }
- manageall=false
- }
- function expand_or_collapse_level (o_tree, level)
- {
- for (var i = 1; i < o_tree.a_index.length; i++)
- {
- var o_item = o_tree.a_index[i];
- if (o_item.n_depth==level && o_item.a_children.length>0 )
- o_item.open(true);
- }
- }
- //For ScriptManage by ghqian 2007-10-25
- if( typeof(Sys)!="undefined" && Sys.Application) {
- Sys.Application.notifyScriptLoaded();
- }
注:最后四行代码用于在.net里使用UpdatePanel的情况。
(二)定义树的图标(treeIcons.js)
在该文件里定义了一个数组tree_tpl,描述了树的节点用什么图片来表示。在定义树的时候需要传入这个数组作为参数。
- var tree_tpl = {
- 'target' : 'elist', // name of the frame links will be opened in
- // other possible values are: _blank, _parent, _search, _self and _top
- 'icon_e' : '../icons/empty.gif', // empty image
- 'icon_l' : '../icons/line1.gif', // vertical line
- 'icon_48' : '../icons/uppost.gif', // root icon normal
- 'icon_52' : '../icons/postopen.gif', // root icon selected
- 'icon_56' : '../icons/uppost.gif', // root icon opened
- 'icon_60' : '../icons/postopen.gif', // root icon selected
- 'icon_16' : '../icons/uppost.gif', // node icon normal
- 'icon_20' : '../icons/postopen.gif', // node icon selected
- 'icon_24' : '../icons/uppost.gif', // node icon opened
- 'icon_28' : '../icons/postopen.gif', // node icon selected opened
- 'icon_0' : '../icons/post.gif', // leaf icon normal
- 'icon_4' : '../icons/post.gif', // leaf icon selected
- 'icon_8' : '../icons/post.gif', // leaf icon opened
- 'icon_12' : '../icons/post.gif', // leaf icon selected
- 'icon_2' : '../icons/joinbottom1.gif', // junction for leaf
- 'icon_3' : '../icons/join1.gif', // junction for last leaf
- 'icon_18' : '../icons/plusbottom1.gif', // junction for closed node
- 'icon_19' : '../icons/plus1.gif', // junctioin for last closed node
- 'icon_26' : '../icons/minusbottom1.gif',// junction for opened node
- 'icon_27' : '../icons/minus1.gif' // junctioin for last opended node
- };
- //For ScriptManage by Jasson Qian 2007-10-25
- if( typeof(Sys)!="undefined" && Sys.Application) {
- Sys.Application.notifyScriptLoaded();
- }
(三)支持C#下生成Script树节点数组的NodeDyn.cs
- using System;
- using System.Collections.Generic;
- using System.Collections;
- public sealed class NodeDyn
- {
- public string name;
- public string url;
- public string data;
- public bool haveChild = false;
- public ArrayList childNode;
- public NodeDyn parentNode = null; //add by ghqian 2007-9-9
- public NodeDyn()
- {
- childNode = new ArrayList();
- }
- public NodeDyn AddChildNode(NodeDyn cNode)
- {
- this.childNode.Add(cNode);
- cNode.parentNode = this; //add by ghqian 2007-9-9
- return this;
- }
- public string GetScript(NodeDyn node)
- {
- string m_sFunction = "";
- m_sFunction += @"<SCRIPT language='javascript'>";
- m_sFunction += @"var TREE_ITEMS =[";
- m_sFunction += AddItem(node).Replace(" ", " ");
- m_sFunction = m_sFunction.TrimEnd(",".ToCharArray());
- m_sFunction += "];";
- m_sFunction += @"</SCRIPT>";
- return m_sFunction;
- }
- private string AddItem(NodeDyn node)
- {
- string str = "";
- if (node.childNode.Count != 0)
- str += "['" + node.name + "','" + node.url + "'," + node.haveChild.ToString().ToLower() + ",'" + node.data + "',"; //Add the last "," by ghqian 2007-9-1
- else if( node.parentNode==null ) //add by ghqian 2007-9-9
- {
- str += "['" + node.name + "','" + node.url + "'," + node.haveChild.ToString().ToLower() + ",'" + node.data + "']";
- }
- for (int i = 0; i < node.childNode.Count; i++)
- {
- NodeDyn cNode = (NodeDyn)node.childNode[i];
- if (cNode.childNode.Count == 0)
- {
- if (i == node.childNode.Count - 1)
- str += "['" + cNode.name + "','" + cNode.url + "'," + cNode.haveChild.ToString().ToLower() + ",'" + cNode.data + "']";
- else
- str += "['" + cNode.name + "','" + cNode.url + "'," + cNode.haveChild.ToString().ToLower() + ",'" + cNode.data + "'],";
- }
- str += AddItem(cNode);
- }
- if (node.childNode.Count != 0)
- str += "],";
- return str;
- }
- /// <summary>
- /// add by ghqian 2007-1-19
- /// 将当前结点生成一个数组字符串,如"['name','url',true,'id']"
- /// </summary>
- /// <returns></returns>
- public string GetString()
- {
- return String.Format("['{0}','{1}',{2},'{3}']", this.name, this.url, this.haveChild.ToString().ToLower(), this.data).Replace(" ", " ");
- }
- }
(四) 生成初始化就要显示的树节点的JavaScript数组并输出到HTML(示例中只显示根节点,在展开的时候自动展开到第一级)
HTML:
- <%-- 在后台生成初始化树的Script代码 --%>
- <asp:Literal ID="ltScript" runat="server" />
C#的Load事件中生成要显示的节点:
- protected void Page_Load(object sender, EventArgs e)
- {
- // 注册Ajax
- AjaxPro.Utility.RegisterTypeForAjax(typeof(Default));
- //创建根节点
- NodeDyn root = new NodeDyn();
- root.name = "Dynamical Load Tree Root";
- root.data = "0";
- root.haveChild = true;
- // 将根节点的Script数组输出到页面: var TREE_ITEMS =[['Dynamical Load Tree Root','',true,'0']];
- ltScript.Text = root.GetScript(root);
- }
运行后输出到页面的代码为:
- <SCRIPT language='javascript'>var TREE_ITEMS =[['Dynamical Load Tree Root','',true,'0']];</SCRIPT>
(五)C#注册Ajax并注册GetChild方法
在C#中我们需要实现一个GetChild方法(名称不一定是GetChild,前后一致即可),能够根据树节点生成该节点的子节点的数组。
首先需要在项目中引用AjaxPro.2.dll,并且在web.config的httpHandlers节中增加
- <httpHandlers>
- <add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro.2"/>
- </httpHandlers>
其次在页面的Load事件中注册Ajax
AjaxPro.Utility.RegisterTypeForAjax(typeof(Default));
然后就注册并实现GetChild方法:
- [AjaxPro.AjaxMethod]
- public string GetChild(string parentId)
- {
- StringBuilder sb = new StringBuilder();
- DataRow[] drs = DtTreeData.Select(" ParentID=" + parentId);
- sb.Append("[");
- for (int i = 0; i < drs.Length; i++ )
- {
- NodeDyn node = GetNode(drs[i]);
- sb.Append(node.GetString());
- sb.Append(",");
- }
- return sb.ToString().TrimEnd(',') + "]";
- }
(六)在HTML中初始化树(aspx文件中)
首先定义一个JavaScript函数,来捕获用户点击树节点的操作
- // 当用户点击树节点时响应的事件
- function OnTreeItemClick( name)
- {
- document.getElementById("tdText").innerText = "Selected Item: " + name;
- }
但学要在生成节点时将给事件注册给node.url。 我们可以在这个事件中做任何你想做的事情,例如打开新的网页等等。
然后定义一个getChild的JavaScript函数来实现取得子节点的方法:
- // 取某个节点的下级节点
- function getChild( parentId )
- {
- var items = eval(DynLoadTree.Default.GetChild( parentId ).value); // 通过Ajax调用后台代码
- return items;
- }
接下来就是定义并初始化树了:
- function loadTree()
- {
- if( TREE_ITEMS!=null && TREE_ITEMS.length>0 )
- {
- var my_tree = new tree (TREE_ITEMS, tree_tpl, getChild );
- expand_or_collapse_level (my_tree,0);
- }
- }
- //加载树
- loadTree();
(七)其他用法:
(1) 有时候我们需要在指定的地方显示树(例如在一个指定的<td></td>内来显示树),我们可以将loadTree()方法放到要显示树的地方来实现,另外还可以在定义tree时添加一个参数,将要显示树的容器ID传给tree:
- var my_tree = new tree (TREE_ITEMS, tree_tpl, getChild, "containerId" );
(2)通过代码来选择一个节点:
- my_tree.selectByData( data, runClientEvent )
如果runClientEvent为true,则选择该节点的同时执行对应的树节点点击的客户端事件,即本示例中的OnTreeItemClick()。
当然,treeDyn.js中还有很多函数可以使用,文件头部的几个全局变量也起了相应的控制作用。重要的是这个思路,在实际应用中可以灵活变通,不要拘泥于本示例。
- 利用Ajax实现树的动态加载
- 利用ExtJS Tree的TreeNode(Json格式)在Struts 2中实现Ajax真正的动态异步加载树
- 利用反射实现类的动态加载
- 利用反射实现类的动态加载
- 利用反射实现类的动态加载
- 利用反射实现类的动态加载
- 利用Dojo和JSON建立无限级AJAX动态加载的功能模块树
- 利用Dojo和JSON建立无限级AJAX动态加载的功能模块树
- 利用Ajax实现动态级联
- ajax动态树的实现
- Ajax+XML实现级联下拉菜单的动态加载
- 利用Jquery的load函数实现页面的动态加载
- 利用反射实现类的动态加载(程序改正)
- 利用动态加载实现手机淘宝的节日特效
- 利用动态加载实现手机淘宝的节日特效
- 利用动态加载实现手机淘宝的节日特效
- ajax动态加载的图标
- Ajax+dtree实现动态加载无刷新树
- C# 开发站点自动登陆工具
- 天气转凉,一切开始起了变化
- ASCII
- 数据库点滴
- Java 2 入门经典(线程、线程同步示例代码)
- 利用Ajax实现树的动态加载
- 用JavaScript验证密码的强弱
- 我的智力游戏人生
- 数据库各厂商发展历史(3. Sybase ASE)
- 经典SQL语句集锦
- 最近写了一个恶意软件,成功绕过了symantec和360,庆祝一下
- 定位new 表达式
- 通过代理类调用webservice
- 为何国内管理软件开发者很艰难?