31.5.3 使用treenode类
treenode类的代码如程序清单31-5所示。(在这里,我们会发现它非常有用,查阅第6章回忆它是如何工作的。)
程序清单31-5 treenode_class.php函数库中的treenode类——该应用程序的主体架构
<?php
//functions for loading,constructing and
//displaying the tree are in this file
class treenode{
//each node in the tree has member variables containing
//all the data for a post except the body of the message
public$m_postid;
public$m_title;
public$m_poster;
public$m_posted;
public$m_children;
public$m_childlist;
public$m_depth;
public function__construct($postid,$title,$poster,$posted,
$children,$expand,$depth,$expanded,$sublist){
//the constructor sets up the member variables,but more
//importantly recursively creates lower parts of the tree
$this->m_postid=$postid;
$this->m_title=$title;
$this->m_poster=$poster;
$this->m_posted=$posted;
$this->m_children=$children;
$this->m_childlist=array();
$this->m_depth=$depth;
//we only care what is below this node if it
//has children and is marked to be expanded
//sublists are always expanded
if(($sublist||$expand)&&$children){
$conn=db_connect();
$query="select*from header where
parent='".$postid."'order by posted";
$result=$conn->query($query);
for($count=0;$row=@$result->fetch_assoc();$count++){
if($sublist||$expanded[$row['postid']]==true){
$expand=true;
}else{
$expand=false;
}
$this->m_childlist[$count]=new treenode($row['postid'],
$row['title'],$row['poster'],$row['posted'],
$row['children'],$expand,$depth+1,$expanded,
$sublist);
}
}
}
function display($row,$sublist=false){
//as this is an object,it is responsible for displaying itself
//$row tells us what row of the display we are up to
//so we know what color it should be
//$sublist tells us whether we are on the main page
//or the message page.Message pages should have
//$sublist=true.
//On a sublist,all messages are expanded and there are
//no"+"or"-"symbols.
//if this is the empty root node skip displaying
if($this->m_depth>-1){
//color alternate rows
echo"<tr><td bgcolor=\"";
if($row%2){
echo"#cccccc\">";
}else{
echo"#ffffff\">";
}
//indent replies to the depth of nesting
for($i=0;$i<$this->m_depth;$i++){
echo"<img src=\"images/spacer.gif\"height=\"22\"
width=\"22\"alt=\"\"valign=\"bottom\"/>";
}
//display+or-or a spacer
if((!$sublist)&&($this->m_children)&&
(sizeof($this->m_childlist))){
//we're on the main page,have some children,and they're expanded
//we are expanded-offer button to collapse
echo"<a href=\"index.php?collapse=".
$this->m_postid."#".$this->m_postid."\"><img
src=\"images/minus.gif\"valign=\"bottom\"
height=\"22\"width=\"22\"alt=\"Collapse Thread\"
border=\"0\"/></a>\n";
}else if(!$sublist&&$this->m_children){
//we are collapsed-offer button to expand
echo"<a href=\"index.php?expand=".
$this->m_postid."#".$this->m_postid."\"><img
src=\"images/plus.gif\"valign=\"bottom\"
height=\"22\"width=\"22\"alt=\"Expand Thread\"
border=\"0\"/></a>\n";
}else{
//we have no children,or are in a sublist,do not give button
echo"<img src=\"images/spacer.gif\"height=\"22\"
width=\"22\"alt=\"\"valign=\"bottom\"/>\n";
}
echo"<a name=\"".$this->m_postid."\"><a href=
\"view_post.php?postid=".$this->m_postid."\">".
$this->m_title."-".$this->m_poster."-".
reformat_date($this->m_posted)."</a></td></tr>";
//increment row counter to alternate colors
$row++;
}
//call display on each of this node's children
//note a node will only have children in its list if expanded
$num_children=sizeof($this->m_childlist);
for($i=0;$i<$num_children;$i++){
$row=$this->m_childlist[$i]->display($row,$sublist);
}
return$row;
}
}
?>
该类包含主应用程序中用来驱动树形视图的功能。
treenode类的一个实例包括关于帖子及到所有对该帖子回复的帖子的链接细节信息。这样,我们就必须为该类定义如下所示的成员变量:
public$m_postid;
public$m_title;
public$m_poster;
public$m_posted;
public$m_children;
public$m_childlist;
public$m_depth;
请注意,treenode并不包含文章的正文。在用户访问view_post.php脚本之前,没有必要载入文章的正文。我们需要尽量使这些运行得相对较快,因为在显示树型列表时,我们还需要完成许多的数据操作,而且在页面刷新时或者按下某个按钮时需要重新计算。
这些变量的命名方法遵循在面向对象应用程序中普遍使用的命名规则——变量名称以m_开始提醒我们它们是类的成员变量。
这些变量的大部分直接对应于数据库中header表的数据行。$m_childlist和$m_depth变量是例外。我们用变量$m_childlist来保存该文章的回复。而变量$m_depth保存我们深入树的层次数——这个信息可以用来创建视图。
该类的构造函数将设置所有变量的值,如下所示:
public function__construct($postid,$title,$poster,$posted,$children,
$expand,$depth,$expanded,$sublist){
//the constructor sets up the member variables,but more
//importantly recursively creates lower parts of the tree
$this->m_postid=$postid;
$this->m_title=$title;
$this->m_poster=$poster;
$this->m_posted=$posted;
$this->m_children=$children;
$this->m_childlist=array();
$this->m_depth=$depth;
当我们在主页面的display_tree()函数中构造根treenode节点时,实际上创建了一个虚节点,没有文章与之相关。我们将传递一些初始值:
$tree=new treenode($start,'','','',1,true,-1,$expanded,$sublist);
以上代码将创建一个$postid为0的根节点。它可用来找出所有第一级的文章,因为它们有一个postid为0的父节点。我们将它的深度设为-1是因为该节点实际上并不是视图的一部分。所有第一级的帖子的深度都为零,处于屏幕的最左端。接下来的深度慢慢向右扩展。
在该构造函数中,最重要的一点是当前节点的子节点被初始化了。该过程中我们首先检查是否需要扩展子节点。仅当某节点有子节点时才执行该过程,并已选择了显示它们:
if(($sublist||$expand)&&$children){
$conn=db_connect();
接下来,我们将连接到数据库,并取出所有子文章,如下所示:
$query="select*from header where parent='".$postid."'order by posted";
$result=$conn->query($query);
然后,使用treenode类的实例填充$m_childlist数组,该数组将包含存储在该节点中所有的回复,如下所示:
for($count=0;$row=@$result->fetch_assoc();$count++){
if($sublist||$expanded[$row['postid']]==true){
$expand=true;
}else{
$expand=false;
}
$this->m_childlist[$count]=new treenode($row['postid'],$row['title'],
$row['poster'],$row['posted'],$row['children'],$expand,
$depth+1,$expanded,$sublist);
}
最后一行代码将创建新的treenode节点,它完全遵照前面讨论过的过程,然而这是对于该树的下一个层次来说的,这里是递归部分。一个父节点调用treenode的构造函数,将自身的postid作为父节点传递,并在传递前将自身的深度加1。
依次创建每个树节点及它的子节点,直到不再有回复或者到达我们想要扩展的深度为止。
完成以上操作后,我们将调用根节点的显示函数(回顾display_tree()函数),如下所示:
$tree->display($row,$sublist);
display()函数首先检查它是否是虚根节点:
if($this->m_depth>-1)
通过该方法,可以将虚节点从视图中去除。然而我们并不希望完全跳过根节点。虽然我们不显示它,但它需要通知它的子节点来显示它们自己。
该函数接下来将绘制包含文章的表。它使用取模操作符(%)来判断该行的背景色应该是什么(因此两种背景色需要交替变化):
//color alternate rows
echo"<tr><td bgcolor=\"";
if($row%2){
echo"#cccccc\">";
}else{
echo"#ffffff\">";
}
接下来,该函数使用$m_depth成员变量来计算当前条目应该缩进多少。回顾前面的图例,可以看到,回复的层次越深,缩进得越多。这是通过下面的代码来完成的:
//indent replies to the depth of nesting
for($i=0;$i<$this->m_depth;$i++){
echo"<img src=\"images/spacer.gif\"height=\"22\"
width=\"22\"alt=\"\"valign=\"bottom\"/>";
}
接下来,该函数将判断是否应提供加号或减号或者什么都没有:
//display+or-or a spacer
if(!$sublist&&$this->m_children&&sizeof($this->m_childlist)){
//we're on the main page,have some children,and they're expanded
//we are expanded-offer button to collapse
echo"<a href=\"index.php?collapse=".
$this->m_postid."#".$this->m_postid."\"><img
src=\"images/minus.gif\"valign=\"bottom\"height=\"22\"
width=\"22\"alt=\"Collapse Thread\"border=\"0\"/></a>\n";
}else if(!$sublist&&$this->m_children){
//we are collapsed-offer button to expand
echo"<a href=\"index.php?expand=".
$this->m_postid."#".$this->m_postid."\"><img
src=\"images/plus.gif\"valign=\"bottom\"height=\"22\"
width=\"22\"alt=\"Expand Thread\"border=\"0\"/></a>\n";
}else{
//we have no children,or are in a sublist,do not give button
echo"<img src=\"images/spacer.gif\"height=\"22\"width=\"22\"
alt=\"\"valign=\"bottom\"/>\n";
}
完成以上操作后,将显示该节点的详细内容:
echo"<a name=\"".$this->m_postid."\"><a href=
\"view_post.php?postid=".$this->m_postid."\">".
$this->m_title."-".$this->m_poster."-".
reformat_date($this->m_posted)."</a></td></tr>";
我们改变下一行的颜色:
//increment row counter to alternate colors
$row++;
之后,有一些代码将被包括根节点在内的所有节点执行,如下所示:
//call display on each of this node's children
//note a node will only have children in its list if expanded
$num_children=sizeof($this->m_childlist);
for($i=0;$i<$num_children;$i++){
$row=$this->m_childlist[$i]->display($row,$sublist);
}
return$row;
在这里,又是一个递归函数调用,它将调用每一个节点的子节点来显示它们自身。将当前行颜色传递给它们,当完成显示操作后再将它传递回来,这样就可以保存交替颜色的记录。
以上就是这个类的所有功能。其代码相当复杂。可以通过运行该程序来认识这个类。当对它的功能感到满意时,再返回来研究这些代码。