SitePoint Sponsor

User Tag List

Page 2 of 4 FirstFirst 1234 LastLast
Results 26 to 50 of 83
  1. #26
    Non-Member
    Join Date
    Jan 2004
    Location
    Planet Earth
    Posts
    1,764
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Okay

    I was thinking myself that I could have a Front Controller based on a hierarchy myself actually

    Sort of, like you'd execute a setup file which would build the tree from configuration, and the hierarchy is then cached (serialised ??) for later use.

    So no need to parse the configuration file every page load for example

    One benifit I can think off, is that if you have a top level child, it's children could be what objects are required, etc for the processing of that given action, yes ?

    What approaches are you thinking off btw

  2. #27
    Resident Java Hater
    Join Date
    Jul 2004
    Location
    Gerodieville Central, UK
    Posts
    446
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Widow Maker
    Okay

    I was thinking myself that I could have a Front Controller based on a hierarchy myself actually

    Sort of, like you'd execute a setup file which would build the tree from configuration, and the hierarchy is then cached (serialised ??) for later use.

    So no need to parse the configuration file every page load for example

    One benifit I can think off, is that if you have a top level child, it's children could be what objects are required, etc for the processing of that given action, yes ?

    What approaches are you thinking off btw
    Well, with any luck I'm going to team up with the LIMB project, and work on porting LIMB to PHP5. Basically, I plan on using that tree system to I pointed out to build the site tree. This gets held in a DB table. There is then another DB table so each tree node can have any number of attributes. Attributes can be set from different sub systems. These sub systems are intercepting filters, such as Access control list based permissions, cache control, logging, etc. These filters can work as pre and/or post conditions for the model.

    Each node in the tree has an object type (like a MIME type for a file system), which sets what module / business model is used to process. That way instead of having a nasty &module= thing in the URL like *Nuke controllers have, you have have a path to the object in the DB in the URL string. With URL rewritting we can hide the index.php front controller.

    I was planning to have use something like a XML file parsed/compiled as PHP to handle site configuration (such as DB connections etc). This XML file can be placed outside the document_root, or be hidden using .htaccess etc.

    The reason I hold the site tree in the DB is it is easy to then query parent nodes. This is important as access control lists depend on "inheritance" from parent nodes to work (not to mention this is a powerful control feature).

    Fancy teaming up then to work on something. There seems to be some common goals here :P

  3. #28
    SitePoint Enthusiast
    Join Date
    May 2003
    Location
    Poland
    Posts
    89
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Off Topic:

    Creating tree structures in javascript and exporting them to php or sql: http://gosu.pl/demo/mygosumenu/1.5/example2.html

  4. #29
    SitePoint Addict
    Join Date
    Mar 2003
    Location
    Germany
    Posts
    216
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Widow Maker, I was able to spend a little time on the problem. May post some more later on. So far, I have managed to determine the breadcrumb trail just like I envisaged it, i.e. with no additional query.

    I just wonder: how do I determine the level of a node from the left and right values? Appears to be possible?! Have posted this question in the database forum, maybe somebody there knows more about it than I.

    By the way, I felt a bit overawed by the problem at first and didn't know where to begin, but once again unit testing came to the rescue, because it allowed me to take one tiny step at a time without knowing where I would end. It's really fantastic!

    Anyway, a little bit of (really early and uncleaned, but working) code to demonstrate how I determine the parents of a node without SQL and without recursion and with a plain array that contains all nodes (which I got with 1 SQL call and which I will also use for the menu later).

    PHP Code:
      /**
       *  Menu Helper Class 
       *  Provides functionality for a tree-based menu
       *  @package ViewHelpers
       */
      
    class MenuHelper {
       
           
    /**
          * Holds all menu items
          * @var array
          * @access private
          */
           
    var $_aMenuItems = array();
          
          
    /**
          * Name of currently active page
          * @var String
          * @access private
          */
           
    var $_sActivePage;
          
          var 
    $_iNumItems;
          var 
    $_aRawTrail = array();
          var 
    $_aKeyIndex = array();
          var 
    $_aFlippedKeyIndex = array();
      
      
    // CREATORS  
          /**
          * Constructs the TrailWriter
          * @param $oRecSet DB RecordSet containing all menu items
          */
          
    function MenuHelper (& $oRecSet) {
              
    $oRecSet->reset();
              while (
    $oRecSet->next()) {
                  
    $aLine $oRecSet->export();
                 
    $this->_aMenuItems[$oRecSet->get('PageName')] = $aLine;
              }
              
    $this->_iNumItems sizeof($this->_aMenuItems);
              
    $this->_aKeyIndex array_keys($this->_aMenuItems);
              
    $this->_aFlippedKeyIndex array_flip($this->_aKeyIndex);
          }
      
      
    // ACCESSORS  
          /**
          * Returns an array containing list items for the trail
          * @param $oWriter A trail writer object
          * @param $page Name of currently active page
          * @access public
          */
          
    function getHTMLTrailMap ($oWriter$page) {
              require_once(
    'class.to.menuto.inc.php');
              
    $this->_sActivePage $page;
      
              
    // add active page to trail
              
    $aCurrItem $this->_aMenuItems[$this->_sActivePage];
              
    $this->_addItemToTrail($aCurrItem);
              
              
    // add parents to trail (if this is not the root element)
              
    $this->_addParentsToTrail($aCurrItem);
              
              
    $oWriter->setModel(array_reverse($this->_aRawTrail));
              return 
    $oWriter->render();
              
          }
          
          function 
    _addItemToTrail($aItem) {
              
    $oTO =& new MenuTO();
              
    $oTO->setLinkText($aItem['LangRSCValue_De_De']);
              
    $oTO->setLinkHref($aItem['PageHREF']);
              
              
    $this->_aRawTrail[] = $oTO;
          }
          
          function 
    _addParentsToTrail($aBaseItem) {
              
              
    $iBaseLeftVal $aBaseItem['NavMenuItemLft'];
              
    $iBaseRightVal $aBaseItem['NavMenuItemRgt'];
              
              
    $p $aBaseItem['PageName'];
                      
              
    // root element has no parents
              
    if ($iBaseLeftVal == 1) {
                  return;
              }
              
              
    $page $aBaseItem['PageName'];
              
    $iLoopStart $this->_aFlippedKeyIndex[$page] - 1;
              
              
    // loop backwards through items to quickly find parents, 
              // beginning with node before active node
              
    for ($i $iLoopStart$i >= 0$i--) {
                  
    $sItemKey $this->_aKeyIndex[$i];
                  
    $aCurrItem $this->_aMenuItems[$sItemKey];
                  
    $iLeftVal $aCurrItem['NavMenuItemLft'];
                  
    $iRightVal $aCurrItem['NavMenuItemRgt'];
                 if ((
    $iLeftVal $iBaseLeftVal) && ($iRightVal $iBaseRightVal)) {
                     
    $this->_addItemToTrail($aCurrItem);
                  }
              }
          }
      
      } 
    (Actual Menu functionality not implemented yet, only the trail). This is the very first implementation and it may not be optimal, but it works. As you can see, I have to do some array mangling in order to a) have fast access to an element by its name and b) be able to iterate numerically through the same array, but I think that's OK.
    The class is given a (wact) RecordSet with all nodes upon construction. The getHTMLTrailMap function take the name of the currently active page and a trail writer object that knows what to do with the "raw" data and produces something that can be used for the view layer (to make it a bit more implementation independent). The trail writer object for my current site looks like this:

    PHP Code:
      /**
       *  Trail Writer Class 
       *  Builds an array containing HTML items that are used for a breadcrumb trail
       *  @package ViewHelpers
       */
      
    class TrailWriter {
       
           
    /**
          * Holds the trail result array.
          * @var array
          * @access private
          */
           
    var $_aTrailRes = array();
          
          
    /**
          * Holds the trail input array.
          * @var array
          * @access private
          */
           
    var $_aInput = array();
      
      
    // CREATORS  
          /**
          * Constructs the TrailWriter
          * @param none
          */
          
    function TrailWriter () {
          }
      
      
    // ACCESSORS  
          /**
          * Returns an array containing list items for the trail
          * @param none
          * @access public
          */
          
    function render () {
              
    $this->_process();
              return 
    $this->_aTrailRes;
          }
      
      
    // MANIPULATORS 
          /**
          * Sets Input array
          * @param $aInput Array containing all trail items (as transfer objects)
          * @access public
          */
          
    function setModel ($aInput = array() ) {
              
    $this->_aInput $aInput;
          }
          
          
    /**
          * Processes the input 
          * @param none
          * @access private
          */
          
    function _process () {
              
    $iSize sizeof($this->_aInput);
              for (
    $i 0$i $iSize$i++) {
                  
    $oItem $this->_aInput[$i];
                  if (
    $i < ($iSize-1)) {
                  
    $this->_aTrailRes[$i] = '<a href="' $oItem->getLinkHref() .
                                    
    '">' $oItem->getLinkText() . '</a> &raquo; ';
                  }
                  else {
                     
    $this->_aTrailRes[$i] = $oItem->getLinkText();
                  }
              }
          }
      } 
    In my case, the writer procuces an array that contains list elements (strings). The last trail item is not wrapped with a link. This array acts as a data source for a list in a wact template. Another writer could output a fully formed trail as a HTML string, for example.
    But these are just implementation details, what was important was the algorithm to determine the parents of a node.
    Next will be the menu implementation itself, for which I intend to use the same approach.
    And to all: Thanks for all the links and input, it's all appreciated! Also like the JavaScript Tree menu, I think from a user perspective this would be optimal for managing the menu! (no page reloads etc). Unfortunately I don't know a thing about JavaScript. Will have a closer look though, when I get to the backend (for the time being, I have input the whole tree structure by hand....).

  5. #30
    SitePoint Addict
    Join Date
    Mar 2003
    Location
    Germany
    Posts
    216
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Looks like there are two related PEAR packages:

    http://pear.php.net/package-info.php?pacid=187
    http://pear.php.net/package-info.php?pacid=104

    Have not looked at them yet, though.

    Still wacking my brain on how to get the level from the lft/rgt values...

  6. #31
    SitePoint Addict
    Join Date
    Mar 2003
    Location
    Germany
    Posts
    216
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    A few seconds later.... Just had an inspiration! You determine the path to a node, i.e. all parents (+ the node itself) and then you count the resulting elements. Voilà, the LEVEL!

    Might be a bit expensive though to do this for every element? But then again, I could then store this path for every element and cache it somehow?
    Which brings me back to a nagging question in the back of my mind: If I somehow store/cache all this in the file system, as a serialized object or whatever, will access to it be faster than querying the db? I always thought that db queries were still faster than any file access?

    Or maybe, whenever I update the db table (which will be rare for a menu), I immediately store this path info and the level in the table to get really fast read performance? No computations needed anymore? Always more questions...

  7. #32
    SitePoint Addict
    Join Date
    Mar 2003
    Location
    Germany
    Posts
    216
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Widow Maker, one more thing: It looks like you recreate the tree in memory? Whereas I just store the data in a "flat" array? Why do you recretae the tree? Is there any advantage to this?

  8. #33
    Resident Java Hater
    Join Date
    Jul 2004
    Location
    Gerodieville Central, UK
    Posts
    446
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by mkrz
    A few seconds later.... Just had an inspiration! You determine the path to a node, i.e. all parents (+ the node itself) and then you count the resulting elements. Voilà, the LEVEL!

    Might be a bit expensive though to do this for every element? But then again, I could then store this path for every element and cache it somehow?
    Which brings me back to a nagging question in the back of my mind: If I somehow store/cache all this in the file system, as a serialized object or whatever, will access to it be faster than querying the db? I always thought that db queries were still faster than any file access?

    Or maybe, whenever I update the db table (which will be rare for a menu), I immediately store this path info and the level in the table to get really fast read performance? No computations needed anymore? Always more questions...
    Celko's sets is a volatile encoding system. That is the encoding data changes everytime you insert data. You might want to look at Vadim Tropashko's nested intervals system, as that uses a materialized path system which is encoded into a system similar to celko's sets using continous rational fractions. That way you can decode the path from the left / right values. It's not the most practical system to implement in MySQL though :S ( Maybe Harry / Marcus / J.Sweat / Jeff could come up with some way :P )

    -- Jason

  9. #34
    SitePoint Addict
    Join Date
    Mar 2003
    Location
    Germany
    Posts
    216
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Blimey, this is slowly getting out of hand... Dont tell me that this is not CS hardcore...

    ...
    We'll describe further details of the mapping by induction. For each node of the tree, let's first define two important points at the xy plane. The depth-first convergence point is an intersection between the diagonal and the vertical line through the node. For example, thedepth-first convergence point for <x=1,y=1/2> is <x=1,y=1>. The breadth-first convergence point is an intersection between the diagonal and the horizontal line through the point. For example, the breadth-first convergence point for <x=1,y=1/2> is <x=1/2,y=1/2>.
    ...
    Uhm, yes.

    But anyway, I think I see what you mean. Celko described a slightly less advanced variant on this (using integer intervals), that also can be used to determine the level. But I'm inclined to think that it wouldn't hurt to have a little redundancy here (i.e. explicitly inserting the level info into the db). It's not so "pure" and it is redundant, but it would save time on reading and displaying the menu. Besides, once I want to change the menu (i.e. not simply insert a new item) I would still have to recompute half of the left/right-values, so maybe there's not so much to be gained with this method. I'll think some more on it, though.

  10. #35
    Resident Java Hater
    Join Date
    Jul 2004
    Location
    Gerodieville Central, UK
    Posts
    446
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by mkrz
    Blimey, this is slowly getting out of hand... Dont tell me that this is not CS hardcore...


    Uhm, yes.
    You haven't got to the good bit.

    Read http://arxiv.org/abs/cs.DB/0401014

    that's the intro for

    http://arxiv.org/pdf/cs.DB/0402051

    Have fun

    -- Jason

  11. #36
    SitePoint Addict
    Join Date
    Mar 2003
    Location
    Germany
    Posts
    216
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Mommy, can I please go back and play with my little menu again?

  12. #37
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    You haven't got to the good bit.
    Man, that's evil. I wonder if for most cms's, it wouldn't suffice to use the adjacency list model, and then create a cache, that stores material paths. Every updating action would just clear the cache (or part of it, if you're really fancy)

  13. #38
    SitePoint Addict
    Join Date
    Mar 2003
    Location
    Germany
    Posts
    216
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    Man, that's evil. I wonder if for most cms's, it wouldn't suffice to use the adjacency list model, and then create a cache, that stores material paths. Every updating action would just clear the cache (or part of it, if you're really fancy)
    Mmh, you don't need fancy approaches like fractional intervals for basic things like a menu.

    Actually I'm almost finished with the whole thing (front-end done, back-end almost done), and I've stuck to the basic modified tree traversal model.
    With the classes WidowMaker referred to, it's quite easy to manage the menu, but it's still a bit of work to wrap it up nicely, since you will want things like a deletion confirmation page, a form to choose the corresponding page for a menu item etc. Can post some code in a few days, if anybody cares. It's certainly not perfect, but it works for me.

  14. #39
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I was thinking something like a seperate table (could be HEAP), that works as an index to the actual table.

    sort of like :
    Code:
    <table name="tree">
        <column name="node_id" type="INTEGER" primaryKey="true" />
        <column name="parent_id" type="INTEGER" />
        <column name="name" type="VARCHAR" />
    </table>
    <table name="tree_index" type="HEAP">
        <column name="node_id" type="INTEGER" primaryKey="true" />
        <column name="material_path" type="VARCHAR" />
    </table>
    You would then have a seperate nodeFinder class, that takes a path, and returns the corresponding node_id. If the path is NOT found in tree_index, a recursive lookup is performed against the master table (tree).

  15. #40
    Non-Member Big Fat Bob's Avatar
    Join Date
    Sep 2004
    Location
    United Kingdom (Come)
    Posts
    79
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yo

    Been reading this thread over the last couple of days, and found this stuff interesting

    Shame Widow isn't around, as I have a few questions, but anyways, Mkrz, could you post any new script that you have ?

    I'd appreciate that, thanks.

  16. #41
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Are you sure WW isn't around "fat bob"...?

  17. #42
    SitePoint Addict
    Join Date
    Mar 2003
    Location
    Germany
    Posts
    216
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    No prob, will post what I have later in the day. Parts of it are still messy (especially the adminsitration part), but I think the front-end side is OK, and it all works now.

  18. #43
    Non-Member Big Fat Bob's Avatar
    Join Date
    Sep 2004
    Location
    United Kingdom (Come)
    Posts
    79
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yo

    Are you sure WW isn't around "fat bob"...?
    I actually think Widow Maker was banned from these forums, why I can't ask questions

    Someone should ask an administrator, just to see

    Mkrz

    Thanks.

  19. #44
    SitePoint Addict
    Join Date
    Mar 2003
    Location
    Germany
    Posts
    216
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    OK, here we go. I'm not quite sure how best to post this, as it's quite a lot of stuff. I think I will post the MenuHelper class here and attach all scripts. Beware though that this will most definitely not work out of the box. It's a bit specific to my situation, my DB table setup, my environment (i.e. WACT). But I think you might be able to get an idea about how I solved this in the end. Maybe you can use it some way, or even improve on it. Maybe you think it's poor, but that's also OK...

    OK, first the MenuHelper class. This class gets an array with all DB rows (which are alrady nicely ordered, no problem there). Then you can call on it to generate a menu or a breadcrumb trail, for which you have to supply a "writer" object that knows how to output the actual result (a view helper, so to speak). For the menu backend, I just wrote another writer, for example. The MenuHelper class is still a bit too tightly tied to my DB tables, for my taste, but I'm not sure yet how to avoid that.
    Also, you have to think about quite a lot of conditions. E.g. what happens when you need a trail for a page that is not in the menu? What happens when you want the menu only to show the content of the active category (and only the cetagory headings for the rest), but don't supply a valid page parameter? And so on. I'm sure I haven't even thought of all problems yet.

    Now, finally, some code (some comments may not be up-to-date, please ignore):

    PHP Code:
    <?php
    require_once('class.to.menuto.inc.php');

    /**
     *  MenuHelper Class 
     *  
     */
    class MenuHelper {
     
         
    /**
        * Holds all menu items
        * @var array
        * @access private
        */
         
    var $_aMenuItems = array();
        
        
    /**
        * Name of currently active page
        * @var String
        * @access private
        */
         
    var $_sActivePage '';
        
        
    /**
        * Number of Menu Items
        * @var String
        * @access private
        */
         
    var $_iNumMenuItems;
        
        
    /**
        * An array containing the raw Trail
        * @var Array
        * @access private
        */
        
    var $_aRawTrail = array();
        
        
    /**
        * An array containing the raw Menu
        * @var Array
        * @access private
        */
        
    var $_aRawMenu = array();
        
        
    /**
        * A helper array containing numeric indexes with the corresponding page name
        * Used to be able to access the menu items by index number
        * @var Array
        * @access private
        */
        
    var $_aKeyIndex = array();
        
        
    /**
        * A helper array containing page names with the corresponding index
        * Used to be able to determine menu item indexes from page name
        * @var Array
        * @access private
        */
        
    var $_aFlippedKeyIndex = array();
        
        
    /**
        * Contains the name of the parent category for the active page
        * @var String
        * @access private
        */
        
    var $_sParentCat;
        
        
    /**
        * Contains page names of all menu items that make up the path from current item to root element
        * @var Array
        * @access private
        */
        
    var $_aPathItems = array();
        
        
    /**
        * Number of all items in the path
        * @var Array
        * @access private
        */
        
    var $_iNumPathItems;
        
        
    /**
        * Wether to display all menu categories or only the active category
        * @var Boolean
        * @access private
        */
        
    var $_bDisplayAllCats false;
        

    // CREATORS  
        /**
        * Constructs the TrailWriter
        * @param $oRecSet DB RecordSet containing all menu items
        * @access public
        */
        
    function MenuHelper ($aInput = array()) {
            
    $this->_aMenuItems $aInput;
            
    $this->_iNumMenuItems sizeof($this->_aMenuItems);
            if ((!
    is_array($this->_aMenuItems)) || ($this->_iNumMenuItems == 0)) {
                die (
    "MenuHelper(): Parameter aInput (aktueller Wert: '$aInput') ist ungültig.");
            }
            
            
    $this->_aKeyIndex array_keys($this->_aMenuItems);
            
    $this->_aFlippedKeyIndex array_flip($this->_aKeyIndex);
        }

    // ACCESSORS  
        /**
        * Returns the trail, as defined by the trail writer
        * @param $oWriter A trail writer object
        * @param $page Name of currently active page
        * @access public
        */
        
    function getTrail ($oWriter$page) {
            
            if (!
    array_key_exists($page$this->_aMenuItems)) { 
                   
    $page $this->_aKeyIndex[0]; // fall back gracefully to root element  
            
    }     
                    
            if (
    $this->_sActivePage != $page) {
                
    $this->_sActivePage $page;
                
    $this->_buildRawTrail();
            }
                    
            
    $oWriter->setModel(array_reverse($this->_aRawTrail));
            return 
    $oWriter->render();
            
        }
        
        
    /**
        * Returns the menu
        * @param $oWriter A Menu writer object
        * @param $page Name of currently active page
        * @param $allcats whether to display nodes in all categories or only for active category
        * @access public
        */
        
    function getMenu ($oWriter$page ''$allcats true) {
            
            
    $this->_bDisplayAllCats $allcats;
            if (
    $page == '') { 
                
    $this->_bDisplayAllCats true// prevent undefined state
            
    }
            else {
                
    $oWriter->setActivePage($page);
                
    $this->_sActivePage $page;
                if (!
    $this->_bDisplayAllCats) {
                    
    $aCurrItem $this->_aMenuItems[$this->_sActivePage];
                    
    $this->_sParentCat $this->_getParent($aCurrItem);
                }
            }
            
            
    $this->_buildRawMenu();
                    
            
    $oWriter->setModel($this->_aRawMenu);
            return 
    $oWriter->render();
            
        }
            
        
    /**
        * Assembles a "raw" (unformatted) array containing item objects for the menu
        * @param none
        * @access private
        */
        
    function _buildRawMenu() {
            
            
    $this->_aRawMenu = array();
            
    $sCurrentCat '';
            for (
    $i 0$i $this->_iNumMenuItems$i++) {
                
    $sItemKey $this->_aKeyIndex[$i];
                
    $aCurrItem $this->_aMenuItems[$sItemKey];
                
    $bItemhasChildren = (($aCurrItem['NavMenuItemRgt'] - $aCurrItem['NavMenuItemLft']) > 1) ? 0;
                
                if (
    $bItemhasChildren) {
                    
    $sCurrentCat $sItemKey;
                }
                
                
    $bAddingIsOK = ($bItemhasChildren || 
                                
    $this->_bDisplayAllCats || 
                                (
    $sCurrentCat === $this->_sParentCat)
                                );
                
                if (
    $bAddingIsOK){
                    
    $oTO =& $this->_assembleTO($aCurrItem);
                    
    $this->_aRawMenu[] = $oTO;
                }
                
            }
        }
        
        
    /**
        * Assembles a "raw" (unformatted) array containing item objects for the trail
        * @param none
        * @access private
        */
        
    function _buildRawTrail() {
            
    $this->_aRawTrail = array();
            
    $aCurrItem $this->_aMenuItems[$this->_sActivePage];
            
            
    $this->_assemblePathItems($aCurrItem);
            
            for (
    $i 0$i $this->_iNumPathItems$i++) {
                
    $aCurrItem $this->_aMenuItems[$this->_aPathItems[$i]];
                
    $oTO =& $this->_assembleTO($aCurrItem);
                
    $this->_aRawTrail[] = $oTO;
            }
        }
        
        
    /**
        * Assembles a Transfer Object (a value object)
        * @param $aItem An array containing the data to be put into the TO
        * @access private
        */
        
    function & _assembleTO($aItem) {
            
    $oTO =& new MenuTO();
            
    $oTO->setID($aItem['NavMenuItemID']);
            
    $oTO->setLevel($aItem['NavMenuItemLevel']);
            
    $oTO->setPageName($aItem['PageName']);
            
    $oTO->setItemText($aItem['LangRSCValue_De_De']);
            
    $oTO->setLinkHref($aItem['PageHREF']);
            return 
    $oTO;
        }
        
        
    /**
        * Assembles all page names for items that make up the path to root node
        * @param $aBaseItem An array containing the base item to start with
        * @access private
        */
        
    function _assemblePathItems($aBaseItem) {
            
    $this->_aPathItems = array();
            
    $iBaseLeftVal $aBaseItem['NavMenuItemLft'];
            
    $iBaseRightVal $aBaseItem['NavMenuItemRgt'];
                        
            
    $page $aBaseItem['PageName'];
            
    $iLoopStart $this->_aFlippedKeyIndex[$page];
            
            
    // loop backwards through items to quickly find parents, 
            // beginning with active node
            
    for ($i $iLoopStart$i >= 0$i--) {
                
    $sItemKey $this->_aKeyIndex[$i];
                
    $aCurrItem $this->_aMenuItems[$sItemKey];
                
    $iLeftVal $aCurrItem['NavMenuItemLft'];
                
    $iRightVal $aCurrItem['NavMenuItemRgt'];
                if ((
    $iLeftVal <= $iBaseLeftVal) && ($iRightVal >= $iBaseRightVal)) {
                    
    $this->_aPathItems[] = $sItemKey;
                }
            }
            
    $this->_iNumPathItems sizeof($this->_aPathItems);
            
        }
        
        
    /**
        * Gets page name of parent of an item
        * @param $aCurrItem An array containing the base item
        * @access private
        */
        
    function _getParent($aBaseItem) {
            
    // root element has no parent
            
    if ($aBaseItem['NavMenuItemLft'] == 1) {
                return 
    '';
            }
            
            
    $this->_assemblePathItems($aBaseItem);
            return 
    $this->_aPathItems[1];
        }
        
    }
    ?>
    The high number of private member variables is probably a code smell, but most methods are fairly short and understandable, I think. I just had to do a lot of array flipping and mangling in order to always get the data fast. and easily.

    For the writer and stuff, have a look at my earlier posts or the attachment in the next post.

  20. #45
    SitePoint Addict
    Join Date
    Mar 2003
    Location
    Germany
    Posts
    216
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Now to the backend:

    For this, I have written another writer class, that gets the menu from the DB and puts some links next to each item (see attached image [sorry - its in German]), for inserting an item, deleting an item or a whole branch etc.
    When I click on the "+" for example, I get to another form where I can choose a page to insert under the current item. With the help of the classes Widow Maker referred to, this is really easy:

    PHP Code:
    $db =& new CDatabase('foo''''''');
    $tree =&  new CDBTree($db'treemenutest''NavMenuItemID');

    switch(
    $action) {
        case 
    "createroot":
            
    $id $tree->clear($aFields);
            
    $Page->set('message'"Root-Element eingefügt");
        break;
        
        case 
    "insertitembelow":
            if (!isset(
    $_REQUEST["parentid"])) {
                die(
    'Kein übergeordnetes Element angegeben.');
            }
            
    $res $tree->insert($_REQUEST["parentid"], $aFields);
            
    $messg $res 'Element eingefügt.' 'Fehler beim Einfügen.';
            
    $Page->set('message'$messg);
        break;
    [...] 
    As I said in an earlier post, you just need some more forms and pages for choosing pages, confirming deletions etc (this part of my code is still really messy, sorry).
    Anyway, hope this helps.
    Attached Images Attached Images
    Attached Files Attached Files

  21. #46
    Non-Member Big Fat Bob's Avatar
    Join Date
    Sep 2004
    Location
    United Kingdom (Come)
    Posts
    79
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks a lot Mkrz, can I pester you for more information ?

    I've never looked at WACT indepth, can you post an example structure, a RecordSet, and some sample scripts that use your classes.

    If you have the time, your database schema will help me a lot as well

    Asking a lot I know but it's appreciated.

  22. #47
    SitePoint Addict
    Join Date
    Mar 2003
    Location
    Germany
    Posts
    216
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    OK, db schema is most easy to get right now. It's more complicated than a standard solution because it's going to be multilingual, so I have to keep a table for all language resources. Most important are the "navmenulist" and "pages" tables. "navmenulist" contains left and rightvalues and level for each node, along with info on what actual page the node is linked to.

    Code:
    CREATE TABLE `navmenulist` (
      `NavMenuItemID` tinyint(4) NOT NULL auto_increment,
      `NavMenuItemLevel` tinyint(1) NOT NULL default '0',
      `NavMenuItemLft` tinyint(4) NOT NULL default '0',
      `NavMenuItemRgt` tinyint(4) NOT NULL default '0',
      `NavMenuItemPage_FK_PageID` tinyint(5) NOT NULL default '0',
      `NavMenuItemText_FK_LangRSCID` int(11) NOT NULL default '0',
      PRIMARY KEY  (`NavMenuItemID`)
    ) TYPE=MyISAM AUTO_INCREMENT=21 ;
    
    CREATE TABLE `pages` (
      `PageID` tinyint(5) NOT NULL auto_increment,
      `PageName` varchar(32) NOT NULL default '',
      `PageHREF` varchar(255) NOT NULL default '',
      `PageTitle` varchar(128) default NULL,
      `PageKeywords` varchar(255) default NULL,
      `PageDesc_FK_LangRSCID` tinyint(5) default NULL,
      PRIMARY KEY  (`PageID`)
    ) TYPE=MyISAM AUTO_INCREMENT=20 ;
    
    
    CREATE TABLE `langrsc` (
      `LangRSCID` int(11) NOT NULL auto_increment,
      `LangRSCGroup_FK_LangRSCGroupID` varchar(32) NOT NULL default '',
      `LangRSCName` varchar(64) NOT NULL default '',
      `LangRSCValue_De_De` varchar(255) NOT NULL default '',
      `LangRSCValue_En_Us` varchar(255) default NULL,
      PRIMARY KEY  (`LangRSCID`)
    ) TYPE=MyISAM AUTO_INCREMENT=20 ;
    
    
    CREATE TABLE `langrscgroups` (
      `LangRscGroupID` int(5) NOT NULL auto_increment,
      `LangRscGroupName` varchar(255) NOT NULL default '',
      PRIMARY KEY  (`LangRscGroupID`)
    ) TYPE=MyISAM AUTO_INCREMENT=3 ;
    I use the MenuHelper script like this (but this needs some optimisation, because I think all this model/data acces code doesn't belong in the view, I'm still struggling with WACT):

    PHP Code:
    require_once WACT_ROOT 'view/view.inc.php';
    require_once 
    WACT_ROOT 'util/arraydataset.inc.php';
    require_once 
    WACT_ROOT '/db/db.inc.php';
    require_once 
    MODEL_PATH 'class.menuhelper.inc.php';
    require_once 
    MODEL_PATH 'class.mkmenuwriter.inc.php';
    require_once 
    MODEL_PATH 'class.trailwriter.inc.php';

    class 
    BaseView extends View {
            
        function 
    BaseView($sTmpl '/home.tpl.htm') {
            
    parent :: View($sTmpl);
            
        }

        function 
    prepare($sActivePage '') {
              
    $oRecSet =& DBC::NewRecordSet('
                            SELECT  * 
                            FROM NavMenuList
                            LEFT JOIN Pages ON NavMenuItemPage_FK_PageID = PageID
                            LEFT JOIN LangRSC ON NavMenuItemText_FK_LANGRSCID = LangRSCID
                            ORDER BY NavMenuItemLft ASC'
    );
            
            
    $oRecSet->reset();
            while (
    $oRecSet->next()) {
                
    $aLine $oRecSet->export();
                
    $aMenuItems[$oRecSet->get('PageName')] = $aLine;
            }
                    
            
    $menu =& new MenuHelper($aMenuItems);
            
    $menuwriter =& new MKMenuWriter();
            
    $trailwriter =& new TrailWriter();
            
            
    $NavMenuDataSet =& new ArrayDataSet($menu->getMenu($menuwriter));
            
    $NavMenuList =& $this->Template->getChild('navmenu');
            
    $NavMenuList->registerDataSet($NavMenuDataSet);
                    
            
    $NavTrailDataSet =& new ArrayDataSet($menu->getTrail($trailwriter$sActivePage));
            
    $NavTrailList =& $this->Template->getChild('navtrail');
            
    $NavTrailList->registerDataSet($NavTrailDataSet);
            
           
        }


    IN order to understand this, you would have to know a bit about the WACT template system. Basically the info is pulled from the DB, passed to the MenuHelper, which gives back an array with "lines" of HTML Code that are ready to be printed out. The array is wrapped in a so-called "DataSet". This Dataset acts as the datasource for a list element in the template that simply iterates over the Dataset and prints out each line the original array contains. But I need to wrap this better. Guess I need at least a DAO to wrap the SQL stuff.

  23. #48
    Non-Member Big Fat Bob's Avatar
    Join Date
    Sep 2004
    Location
    United Kingdom (Come)
    Posts
    79
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yo

    Thanks a lot

    From what I do know of WACT, I can understand the point on the templates, I find this the most impressive part of WACT btw

    You mention about each line being the HTML, which clears up a few things for me, as well as the database schema.

    Again, thanks for all your help. I'm trying to build a tree myself based on what a user has permissions for you see ?

    Thinking that I can store the hierarchy as an array in a session

  24. #49
    Non-Member Big Fat Bob's Avatar
    Join Date
    Sep 2004
    Location
    United Kingdom (Come)
    Posts
    79
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yo

    Still digesting your scripts Mkrz but in the meantime, this is what I have done today;

    PHP Code:
    $tree = new hierarchy;
     
    $tree -> initialise();
     
     if( !
    $nodes $tree -> get_children0) ) {
      
    //-- no children found
     
    }
     
     if( !
    $tree -> has_children0) ) {
      
    //-- no children
     
    }
     
     class 
    hierarchy {
      private 
    $tree;
      
      public function 
    __construct() {
       
    //-- empty
      
    }
      
      public function 
    initialise() {
       
    $finder = new hierarchy_finder;
       
    //-- return whole tree structure
       
    $this -> build$finder -> find_all() );
      }
      
      public function 
    load$source ) {
       if( !
    is_array$source ) ) {
        return;
       }
      }
      
      public function 
    fetch() {
       return 
    $this -> tree;
      }
      
      public function 
    has_parent$lft$rgt ) {
       
    //-- empty
      
    }
      
      public function 
    get_parent$lft$rgt ) {
       
    //-- empty
      
    }
      
      public function 
    has_children$lft$rgt ) {
       
    $numb ceil( ( $rgt $lft -) / );
       
       return ( 
    $numb >= (int) );
      }
      
      public function 
    get_children$lft$rgt ) {
       
    $temp = array();
       
       foreach( 
    $this -> tree as $node ) {
        
    $lt = (int) $node['data']['lft'];
        
    $rt = (int) $node['data']['rgt'];
        
        if( ( 
    $lft < (int) $lt ) && ( $rgt > (int) $rt ) ) {
         
    $temp[] = $node['data'];
        }
       }
       
       if( empty( 
    $temp ) ) {
        return 
    false;
       }
       
       return 
    $temp;
      }
      
      private function 
    build$resultset ) {
       
    $iterator = new sql_iterator$resultset );
       
    $temp = array();
       
    $tree = array();
       
       while( 
    $iterator -> is_valid() ) {
        
    $row $iterator -> current();
        
    $iterator -> next();
        
        if( 
    count$temp ) > (int) ) {
         while( 
    $temp[count$temp ) -1] < $row['rgt'] ) { 
                      
    array_pop$temp ); 
                  }
        }
      
              
    $temp[] = $row['rgt'];
              
              if( 
    $row['id'] > (int) ) {
               
    //-- void default hierarchy node 
               
    $tree[strtolower$row['name'] )]['data'] = $row;
         
    $tree[strtolower$row['name'] )]['level'] = (int) count$temp ) -1;
        } 
       }
       
       
    $this -> tree $tree;
      }
     }
     
     class 
    hierarchy_finder {
      private 
    $conn;
      
      public function 
    __construct() {
       
    $this -> conn dbase::get_instance();
      }
      
      public function 
    find_all() {
       
    $sql 'select distinct 
       
       id,
       url,
       name,
       status,
       required,
       descrip,
       lft,
       rgt
       
       from hierarchy order hierarchy.lft asc'
    ;
       
       
    $stmt $this -> conn -> create_statement$sql );
       
    //-- resultset
       
    return $stmt -> execute();
      }
      
      public function 
    find_by_ref$lft$rgt ) {
       
    //-- empty
      
    }
     }
    ... 
    Not near as clever as your scripts but it's a start

  25. #50
    SitePoint Member
    Join Date
    Nov 2004
    Location
    MyVille
    Posts
    3
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I've posted this question elsewhere... BFB - you probably have seen it.

    What's the best way to fetch a tree in some sort of order? Whether it be by a document's date or, more appropriately, the document's name? I see that using the Modified Preorder Tree you ORDER BY the leftID to return a sorted tree... but the results in a particular level could be un-alphabatized.

    Any ideas?


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •