65.9K
CodeProject 正在变化。 阅读更多。
Home

CSS 友好的 AJAX 树控件

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.20/5 (2投票s)

2008年8月13日

GPL3

4分钟阅读

viewsIcon

25740

downloadIcon

140

一个非常简单且轻量级的 AJAX 分层树形控件。

引言

我正在开发一个 WebPart,我需要一个客户端分层树形控件。 我还想要一个 CSS 友好的特定标记。 然后我发现这个很棒,并让我开始了。 我现在已经这样设置好了,效果很好。 唯一的问题是,我可能处理的一些数据集会非常大。 这个脚本做了一些初始设置,对于大数据来说代价很高。 因此,我衍生出了这段代码。 我不再使用树形控件 JavaScript,但我仍然使用 CSS,并且该代码是我最终代码的基础。

背景

我想指出一些我认为对 Web 开发的所有方面都有帮助的事情。

  1. 命名空间
  2. 请参阅此链接,了解关于为什么这种方法可行的更详细的讨论。 Dustin 还有一本非常有用的书,现在就放在我的桌子上。

    var DE = {
        data : {},
        net : {},
        client : {},
        util : { 
            coll: {}
            },
        widget : { },
        example : { }
    };

    用法: 鉴于今天的代码是框架的子集,因此在您的代码中使用命名空间至关重要。

  3. JavaScript 类
  4. ////DOM class
    DE.util.DOM = function() {
           
       //create private member vars here.
       //Only protected methods have access to these vars 
       //this all falls in the realm of js classes and is not 
       //discussed fully in this article but 
       //can be found all over the web.
    
       //protected method with access to private member vars 
       this.addClass = function() {
            .......
       }
       this.anotherMethod = function() {...........}
    
    }; 
    
    //public method    
    DE.util.DOM.prototype.$ = function() {
    
    using the above class:
    var varName = new DE.util.DOM();
  5. 日志记录
  6. 这非常有用而且必要。 警报很快就会过时!

    var log = new DE.util.Logger(); 
    log.show( true );
    log.showTimeStamp(true);

    现在,记录就像这样简单:

    log.Log("log this now");

    您还需要在您的页面上创建一个 div,其 id="Log"

    <div id=&quot;Log&quot;></div>
  7. 简化的 AJAX 调用
  8. var _cb = new DE.util.HierFilterCB('filterdata' );
    ajax1.setAsyncCall(false); or not Async jax
    ajax1.request('GET',&quot;HierFilterPage.aspx?rnd=&quot;+ Math.random(), _cb );
  9. 服务器端代码
  10. HierFilter hier = null;
    String parent = Request.QueryString[&quot;parent&quot;];
    if (!String.IsNullOrEmpty(parent))
    {
       //if index is not null then a specific request for a node has been made 
       //index is null only on intial load
       hier = new HierFilter(@&quot;C:/Downloads/CssFriendly&quot; + 
                  @&quot;AjaxTreeCtrl/App_Data/hierdata.xml&quot;, Int32.Parse(parent));
       Response.Write( hier.ToString() );
    }

简而言之

客户端最初调用服务器以获取深度为 0 的 XML 数据。

var ajax1 = new DE.util.Ajax();
var _cb = new DE.util.HierFilterCB('filterdata' );
ajax1.setAsyncCall(false);
ajax1.request('GET',&quot;HierFilterPage.aspx?rnd=&quot;+ Math.random(), _cb );

上面的代码不是异步的,因为我们需要确保返回所有数据,因为回调负责解析服务器返回的数据。 客户端调用以下代码块。 服务器通过传递数据(XML)的位置来实例化一个 HierFilter 类型。

//intial load 
hier = new HierFilter(@&quot;C:/Downloads/&quot; + 
           @&quot;CssFriendlyAjaxTreeCtrl/App_Data/hierdata.xml&quot;);
Response.Write(hier.ToString());

HierFilter 类继承自 FilterBase。 在此示例中,FilterBase 负责将处理后的数据格式化回客户端。

HierFilter.cs

public HierFilter( String file) : base()
{
    selectedIndicies = &quot;&quot;;
    XmlWriter w = base.getXmlWriter();
    XmlDocument doc = new XmlDocument();
    doc.Load( file );
    CreateTree(w, doc);
    
}

FilterBase.cs

以下代码块将有助于创建格式良好的 XHTML 数据以发送回客户端。

public XmlWriter getXmlWriter()
{
     //uses class variable sb
     XmlWriterSettings settings = new XmlWriterSettings();
     XmlWriter w;
    //start first <ul> tag 
    //tell the xml parser that i am not abiding by strigent xml rules 
    settings.ConformanceLevel = ConformanceLevel.Fragment;
    //pretty print - not necessary but good for debugging 
    settings.Indent = true;
    //_settings.Encoding = Encoding.Unicode;
    //dont show xml info in header 
    settings.OmitXmlDeclaration = true;
    settings.CloseOutput = false;
    //XmlWriter factory push xml to string builder with these settings 
    w = XmlWriter.Create(sb, settings);
    return w;
}

HierFilter.cs

以下代码在级别 0 创建树数据。 CSS 友好的树标记是一种嵌套的无序列表元素的模式。

<ul id=&quot;mktree&quot;> 
    <li>......</li> 
    <li>......</li>
</ul>

代码

private void CreateTree(XmlWriter w, XmlDocument doc )
{
    //get node list 
   // InitSettings();
    w.WriteStartElement(&quot;ul&quot;); // beginning <ul> tag 
    w.WriteStartAttribute(&quot;class&quot;);
    w.WriteValue(&quot;mktree&quot;);
    w.WriteStartAttribute(&quot;id&quot;);
    w.WriteValue(&quot;hierfilter&quot;);
    GoDeeper(w , doc , 0 );
    w.WriteEndElement(); //</ul> outermost ul tag 
    w.Flush();
}
private void GoDeeper(XmlWriter w , XmlDocument doc, int level )
{
    bool state =false;
    int index = 0;
    bool hasChildren = false;
    //create a tree based on the level provided
    XmlNodeList nodes = doc.SelectNodes(&quot;//n[@d='&quot;+ level +&quot;']&quot;);           
    foreach (XmlNode node in nodes)
    {
        if (node.Attributes.Count == 4)
        {
            //get state from xml 
            if (node.Attributes[3].Value == &quot;0&quot;)
                state = false;
            else
                state = true;
        }
        index = Int32.Parse(node.Attributes[0].Value);
        hasChildren = Boolean.Parse(node.Attributes[2].Value);

        w.WriteStartElement(&quot;li&quot;); // beginning <ul> tag 
        if (hasChildren)
        {
            //if the node has children place a plus gif next to it using css 
            w.WriteStartAttribute(&quot;class&quot;);
            w.WriteValue(&quot;liClosed&quot;);
        }
        w.WriteStartElement(&quot;span&quot;);
        w.WriteStartAttribute(&quot;class&quot;);
        w.WriteValue(&quot;index&quot;);
        w.WriteEndAttribute();
        w.WriteValue(index.ToString());
        w.WriteEndElement();      
        w.WriteStartElement(&quot;span&quot;);
        w.WriteStartAttribute(&quot;class&quot;);
        w.WriteValue(&quot;bullet&quot;);
        w.WriteEndAttribute();
        w.WriteValue( node.InnerText );
        w.WriteEndElement(); 

        //w.WriteValue(node.InnerText );
        w.WriteEndElement();
    }
    //close all oustanding xml tags 
    //CloseOut(w, elementStack);
}

单击节点时会调用以下代码。 这传入父 ID,并以格式良好的 XHTML 格式返回其所有子节点。

public void GetChildren( XmlWriter w, XmlDocument doc, int parent )
{
    bool state = false;
    int index = 0;
    bool hasChildren = false;
   
    //w.WriteStartElement(&quot;ul&quot;); // beginning <ul> tag 
    //w.WriteStartAttribute(&quot;class&quot;);
    //w.WriteValue(&quot;liOpen&quot;);
    //create a tree based on the level provided
    XmlNodeList nodes = doc.SelectNodes(&quot;//n[@p='&quot; + parent + &quot;']&quot;);
    foreach (XmlNode node in nodes)
    {

        if (node.Attributes.Count == 4)
        {
            //get state from xml 
            if (node.Attributes[3].Value == &quot;0&quot;)
                state = false;
            else
                state = true;
        }
        index = Int32.Parse(node.Attributes[0].Value);
        hasChildren = Boolean.Parse(node.Attributes[3].Value);

        w.WriteStartElement(&quot;li&quot;); // beginning <ul> tag 
        if (hasChildren)
        {
            //if the node has children place a plus gif next to it using css 
            w.WriteStartAttribute(&quot;class&quot;);
            w.WriteValue(&quot;liClosed&quot;);
        }
        else
        {
            w.WriteStartAttribute(&quot;class&quot;);
            w.WriteValue(&quot;liBullet&quot;);
        }
        w.WriteStartElement(&quot;span&quot;);
        w.WriteStartAttribute(&quot;class&quot;);
        w.WriteValue(&quot;index&quot;);
        w.WriteEndAttribute();
        w.WriteValue(index.ToString());
        w.WriteEndElement();

        w.WriteStartElement(&quot;span&quot;);
        w.WriteStartAttribute(&quot;class&quot;);
        w.WriteValue(&quot;bullet&quot;);
        w.WriteEndAttribute();
        w.WriteValue(node.InnerText);
        w.WriteEndElement();

        //w.WriteValue(node.InnerText );
        w.WriteEndElement();

    }
   // w.WriteEndElement(); //end of ul of liOpen
    w.Flush();
}

回到启动所有操作的客户端回调。 以下内容位于 success 方法中。 如果 AJAX 调用完成且没有错误,则将调用此方法。 返回的数据格式良好,因此我们解析它并查找某些标准,这些标准将允许 CSS 设置我们正在增长的树的样式。 如果元素具有类名 liClosed,那么我们想将一个 OnClick 事件附加到它。 这转化为一个节点是否具有子节点。 如果一个节点没有子节点,那么我们就不需要调用服务器。 我们如何知道节点是否有子节点? XML 具有一个属性 'hc',它在服务器上进行处理并传递给客户端。

随着树按级别展开,模式会作为嵌套的无序列表增长。

<ul>
     <li> 
        <ul> 
            <li>....../<li>
        </ul>
    </li>
</ul>

代码

if ( n.className == 'liClosed')
{
   n.onclick = function() {
   log.Log(&quot;classname inside HierFilterCB &quot; + n.className );
  //check to see if node has been loaded 
   var ul = n.getElementsByTagName('ul');
   if ( ul.length > 0 ) 
   {
     if ( n.className == 'liClosed') 
     {
       n.className = 'liOpen';
     }else{n.className = 'liClosed';}
   }else
    {                                                     
      var spans = n.getElementsByTagName('span');
      //get the index from here 
      for ( var j =0; j<spans.length;j++)
      {
       if ( spans[j].className == 'index')
       {
         var index = spans[j].innerText;
         var child = new DE.util.ChildItemCB( n );
         var childrenAjax = new DE.util.Ajax();
         childrenAjax.request('GET', 
           'HierFilterPage.aspx?parent='+index,child);
       }
         log.Log(&quot;spans &quot; + spans[j] );
      }
    }   
 }  
}

单击节点时,如果尚未向服务器发出请求,则会调用以下代码。 如果已经请求过,那么上面有一些代码会修改 CSS 来展开或折叠树。

//callback method called when 
//a node is clicked after intial loading is complete. 
//see also  DE.util.HierFilterCB for more info
//parsing is better understood by inspecting hierdata.xml
DE.util.ChildItemCB = function( child ) {        
    this.success = function(val){           
       log.Log(&quot;child className &quot; + child.className ) ;           
       var children = document.createElement('ul')           
       children.innerHTML = val;           
          var li = children.getElementsByTagName('li');
             for ( var i=0; i < li.length; i++ )
                 parseTree(li[i]);

上面的解析树是在服务器返回的每个 <li> 项上调用的。 同样,模式是,如果一个节点有子节点,那么我们需要将一个 OnClick 事件应用到它并修改它的类名,因此允许 CSS 用适当的加号/减号正确地设置我们的树的样式。

if ( n.className == 'liClosed')
{ 
  log.Log(&quot;n &quot;+ n.innerText);
  log.Log(&quot;n.className &quot; + n.className );
  n.onclick = function(e) {
    e = e || (window.event || {});
    e.cancelBubble = true;
    ...................

上面的代码类似于第一次调用服务器。 Accept 必须取消从其父节点继承的任何事件冒泡。

这是一个使用的示例 XML 数据集

<n i='0' d='0' hc='true' s='0'>1  -  Scope</n>
<n i='1' p='0' d='1' hc='false' s='0'>1.1  -  Determine project scope</n>
<n i='2' p='0' d='1' hc='false' s='0'>1.2  -  Secure project sponsorship</n>
<n i='3' p='0' d='1' hc='false' s='0'>1.3  -  Define preliminary resources</n>
<n i='4' p='0' d='1' hc='false' s='0'>1.4  -  Secure core resources</n>
<n i='5' p='0' d='1' hc='false' s='0'>1.5  -  Scope complete</n> 
<n i='6' p='0' d='1' hc='true' s='0'>1.6  -  Program Group</n>
<n i='7' p='6' d='2' hc='false' s='0'>1.6.1  -  Milestone 1</n>
<n i='8' p='6' d='2' hc='false' s='0'>1.6.2  -  Milestone 2</n>
<n i='9' p='6' d='2' hc='false' s='0'>1.6.3  -  Milestone 3</n>

使用代码

下载代码并更改 HierFilterPage.aspx 中 XML 文件的路径,然后从 Default.aspx 运行。

关注点

这仅适用于 IE6,不确定 IE7,但我希望收到有关如何使其与 Firefox 一起使用的反馈。 所以,请,如果有人让它工作,我想看看。 干杯!

这是在 WebDeveloper 2008 中开发的。但是,使用任何基于 Visual Studio 的 IDE 应该很容易,只需删除解决方案文件并从您选择的 IDE 打开项目即可。

历史

首次发布 - 2008 年 8 月 13 日。

© . All rights reserved.