Location: PHPKode > projects > AModules3 > amodules-3.0.1/lib/TreeView.php
<?php
/**
 * TreeView as an object you can use to display some self-dependant data. 
 * TreView uses Ajax technology to expand/collapse its branches.
 */
class TreeView extends Lister{
	protected $row_t;
	protected $id_field='id';
	/**
	 * Contains restructured data (in sequence nodes should be rendered)
	 */
	public $temp_data;
	private $parent_field='parent_id';
	private $display_field = array();
	private $root_value=null;
	protected $level_field = 'tv_level';
	private $collapsed=false;
	private $display_buttons=true;
	
	function init(){
		parent::init();
		$this->row_t=$this->template->cloneRegion('row');
	}
    function setSource($table, $id_field = 'id', $parent_field = 'parent_id', $root_value = null, $db_fields="*"){
    	parent::setSource($table, $db_fields);
    	$this->id_field = $id_field;
    	$this->parent_field = $parent_field;
    	$this->root_value = $root_value;
    	$this->dq->order($this->parent_field);
    	return $this;
    }
    function setStaticSource($data, $id_field = 'id', $parent_field = 'parent_id', $root_value = null){
    	$this->id_field = $id_field;
    	$this->parent_field = $parent_field;
    	$this->root_value = $root_value;
    	$this->temp_data=$data;
    	return $this;
    }
	function defaultTemplate(){
		return array('treeview', '_top');
	}
    function fetchRow(){
        if(is_array($this->data)){
            return $this->getNextItem();
        }
        return false;
    }
    function display($format, $name, $prefix = ''){
    	/**
    	 * Adds a field to display as a branch caption.
    	 * @param format - how to format this field
    	 * @param name - name of a DB-field
    	 * @param prefix - could be used as a separator
    	 */
    	$this->display_field[]=array('name'=>$name, 'prefix'=>$prefix, 'format'=>$format);
    	return $this;
    }
    private function getNextItem(){
    	return (bool)($this->current_row=array_shift($this->data));
    }
    function hideButtons(){
    	/**
    	 * Whether to show expand buttons for branches
    	 */
    	$this->display_buttons=false;
    	return $this;
    }
    function collapseAll(){
    	/**
    	 * Call this method in init() to collapse all the branches by default
    	 */
    	$this->collapsed=true;
    	return $this;
    }
    function expandAll(){
    	/**
    	 * Call this method in init() to expand all the branches by default
    	 */
    	$this->collapsed=false;
    	return $this;
    }
    protected function recurseData($parent_id, $level = 0){
    	foreach($this->temp_data as $key=>&$row){
    		if($row['displayed']&&$row[$this->id_field]!=$parent_id)continue;
    		foreach($row as $field=>$value){
    			if($field == $this->parent_field && $value === $parent_id){
    				$this->temp_data[$key]['displayed'] = true;
    				$row[$this->level_field] = $level;
    				$row['collapsed']=$this->collapsed&&(!isset($row['collapsed'])||$row['collapsed']);
    				$this->data[] = $row;
    				if(!$row['collapsed'])$this->recurseData($row[$this->id_field], $level+1);
    			}
    		}
    	}
    }
    private function getNextLevel(){
    	$row = array_shift($this->data);
    	array_unshift($this->data, $row);
    	return $row?$row[$this->level_field]:-1;
    }
    private function getData($parent_id){
    	if(!$this->temp_data){
    		if(!$this->dq)return false;
	    	//if($this->collapsed)$this->dq->where($this->parent_field,$parent_id);
	    	$this->dq->do_select();
	    	$this->temp_data = $this->dq->do_getAllHash();
    	}
    	$level=0;$id=$parent_id;
    	while($id!=$this->root_value){
    		$level++;
    		$id=$this->getItem($id);
    		$id=$id['parent_id'];
    	}
    	$this->recurseData($parent_id,$level);
    	return true;
    }
    protected function getItem($id){
    	/**
    	 * Returns item's hash
    	 */
    	foreach($this->temp_data as $row){
    		if($row[$this->id_field]==$id)return $row;
    	}
    	return false;
    }
    function isNodeHasChildren($node_id){
    	/**
    	 * Returns true if node with id==$node_id has children.
    	 * Works after data aligning, returns true before
    	 */
    	if(!$this->temp_data)return true;
    	foreach($this->temp_data as $row){
    		if($row[$this->parent_field]==$node_id)return true;
    	}
    	return false;
    }
    function formatItem(){
       	$this->current_row['caption']="";
        foreach($this->display_field as $tmp=>$field){
	        $formatters = split(',',$field['format']);
        	$this->current_row['caption'].=$field['prefix'];
	        foreach($formatters as $formatter){
	            if(method_exists($this,$m="format_".$formatter)){
	                $this->$m($field);
	            }else throw new BaseException("TreeView does not know how to format type: ".$formatter);
	        }
        }
    }
    
    function format_text($field){
    	$this->current_row['caption'].=$this->current_row[$field['name']];
    }
    function format_link($field){
    	/**
    	 * Makes a displaying text a link like PageName_FieldName. 
    	 * You should define a Page descendant to make this link work
    	 */
    	$caption=$this->current_row[$field['name']];
    	$this->current_row['caption'].="<a href=".
    		$this->api->getDestinationURL($this->api->page.'_'.$field['name'], 
    			array('id'=>$this->current_row[$this->id_field])).">" .
    		$caption."</a>";
    }
    
    protected function renderBranch($parent_id){
    	//executing query for a branch
    	if($this->getData($parent_id)===false)return '';
    	$prev_level = -1;
    	$level_on = $this->template->get('level_on');
    	//$level_on=$level_on[0];
    	$level_off = $this->template->get('level_off');
		$this->row_t->set('level_on', $level_on);
    	
    	$this->template->del('rows');
        while($this->fetchRow()){
            $this->formatItem();
           	$this->template->del('level_off');
           	$this->template->del('level_on');

            if($this->current_row[$this->level_field]>$prev_level){
            	$this->row_t->set('level_on', $level_on);
            }
            $next_level=$this->getNextLevel();
            if($next_level<$this->current_row[$this->level_field]){
            	//we may need to change level more then by 1
            	$diff=$this->current_row[$this->level_field]-$next_level;
            	$off='';
            	while($diff>0){
            		$off.=$level_off;
            		$diff--;
            	}
           		$this->row_t->set('level_off', $off);
            }

            //adding a branch expand button
            $this->row_t->set('button_id', 'ec_'.$this->current_row[$this->id_field]);
            $this->row_t->set('ec', $this->getButton($this->current_row['collapsed'], 
            		$this->current_row[$this->id_field]));
            
            $span='<span id="p_'.$this->current_row[$this->id_field].'">';
            if($this->current_row['collapsed']==1)$span.='</span>';
            $this->row_t->set('span', $span);
            
            //$this->row_t->set('parent', 'p_'.$this->current_row[$this->parent_field]);
            $this->row_t->set('content', $this->current_row['caption']);
            $this->template->append('rows',$this->row_t->render());
            $prev_level = $this->current_row[$this->level_field];
        }
        return $this->template->render();
    }
    private function getButton($expand, $id){
    	$expand=$expand?'1':'0';
    	//empty button is one pixel stretched to button size. 
    	$button='<img src="amodules3/templates/shared/pixel.gif" height="9" width="9">';
		if($this->display_buttons){
			if(!$this->isNodeHasChildren($id))return $button;
	    	$onclick=
				/*"aasn('p_".$id."','".
				$this->api->getDestinationURL(null, array(
					'ec'=>$id,
					'cut_object'=>$this->name, 
					'ec_action'=>$expand?'expand':'collapse'
				))."'); " .*/
				"treenode_flip($expand, $id, '".$this->api->getDestinationURL(null, array(
					'ec'=>$id,
					'cut_object'=>$this->name, 
					'ec_action'=>$expand?'expand':'collapse'
				))."')";
			;
	    	$button='<img src="amodules3/templates/kt2/'.($expand?'plus.gif':'minus.gif').'" ' .
	    		'id="button_'.$id.'"'.
				' onclick="'.$onclick.'">';
		}else return '';
		return $button;
    }
    function render(){
		if($_GET['ec']){
			if($_GET['ec_action']=='expand'){
				$this->output($this->renderBranch($_GET['ec']));
			}elseif($_GET['ec_action']=='collapse'){
				$this->template->del('TreeView');
				$this->output('');
			}
		}else{
    		$this->output($this->renderBranch($this->root_value));
		}
    }
}	
Return current item: AModules3