SitePoint Sponsor

User Tag List

Results 1 to 10 of 10
  1. #1
    SitePoint Enthusiast
    Join Date
    Jun 2011
    Posts
    26
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Creating hierarchy of divs with PHP loop?

    Hello,

    Im trying to create an employee chain of command in the form of nested divs showing superiority.

    I start with a single manager ( id=1 ) and then run 3 of these queries inside each other.

    SAMPLE query: SELECT * FROM employees WHERE superior = $superiorID

    Obviously this is wrong way to do it. I cant seem to put together a single loop statement that will put all these employees in their respective nested divs in this hierarchy. But i know this is possible.

    Hopefully this makes sense and somebody can point me in the right direction. Thanks!!

  2. #2
    SitePoint Guru bronze trophy
    Join Date
    Feb 2013
    Posts
    772
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)
    Not sure if this is what you need.
    PHP Code:
    <?php
    $host 
    "localhost";  
    //Database user name.    
    $login "";
    //Database Password.
    $dbpass "";
    //Database name.
    $dbname "";
    $PDO = new PDO("mysql:host=localhost;dbname=$dbname""$login""$dbpass");

    $id 1;
    $employee_names = array();
        
    try{
        
    $sql "SELECT 
        a1.name AS name1,
        a2.name AS name2,
        a3.name AS name3
        
        FROM employees AS a1
        left outer
         join employees AS a2
          ON a2.superior = a1.id   
        left outer
         join employees AS a3
          ON a3.superior = a2.id  
        WHERE a1.id = :id"
    ;
        
    $query $PDO->prepare($sql); 
        
    $query->bindParam(":id"$id); 
        
    $query->execute();
        while(
    $row $query->fetch(PDO::FETCH_ASSOC)){ 
            
    $employee_names[$row['name1']][$row['name2']][] = $row['name3'];
        }    
                 
    }catch (
    PDOException $e){ 
        echo 
    "Database error: ".$e->getMessage(); 
    }  

      
        echo 
    "<pre>";
        
    print_r($employee_names); 
        echo 
    "</pre>";
    ?>
    For my little test it came out like this
    Code:
    Array
    (
        [Boss] => Array
            (
                [Manager] => Array
                    (
                        [0] => Employee1
                        [1] => Employee2
                    )
    
            )
    
    )

  3. #3
    SitePoint Enthusiast
    Join Date
    Jun 2011
    Posts
    26
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Drummin,

    Thanks for that! That would work great, but I want to use a solution that can adapt if there are more levels of command. There will probably be 5 or 6 levels later on.

    I already have something working for the current 3 levels. just looking for the completely versatile loop statement.

    Just need something that will loop through X amount of times and echo out employees into their bosses div. Divs inside divs basically.

    In the end it should look like this (variable amount of employees at each level):
    Code:
    <div class="first">
      <div class="second">
         <div class="third"></div>
         <div class="third"></div>
      </div>
      <div class="second">
         <div class="third"></div>
         <div class="third"></div>
         <div class="third"></div>
         <div class="third"></div>
      </div>
      <div class="second">
         <div class="third"></div>
         <div class="third"></div>
         <div class="third"></div>
      </div>
    </div>
    
    <div class="first">
      <div class="second">
         <div class="third"></div>
         <div class="third"></div>
      </div>
      <div class="second">
         <div class="third"></div>
         <div class="third"></div>
         <div class="third"></div>
         <div class="third"></div>
      </div>
      </div>
    </div>

  4. #4
    SitePoint Guru bronze trophy
    Join Date
    Feb 2013
    Posts
    772
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)
    Through six levels anyway...
    PHP Code:
    <?php
    $host 
    "localhost";  
    //Database user name.    
    $login "";
    //Database Password.
    $dbpass "";
    //Database name.
    $dbname "";
    $PDO = new PDO("mysql:host=localhost;dbname=$dbname""$login""$dbpass");

    $id 1;
    $employee_names = array();
        
    try{
        
    $sql "SELECT 
        a1.name AS name1,
        a2.name AS name2,
        a3.name AS name3,
        a4.name AS name4,
        a5.name AS name5,
        a6.name AS name6
        
        FROM employees AS a1
        left outer
         join employees AS a2
          ON a2.superior = a1.id   
        left outer
         join employees AS a3
          ON a3.superior = a2.id     
        left outer
         join employees AS a4
          ON a4.superior = a3.id     
        left outer
         join employees AS a5
          ON a5.superior = a4.id     
        left outer
         join employees AS a6
          ON a6.superior = a5.id  
        WHERE a1.id = :id"
    ;
        
    $query $PDO->prepare($sql); 
        
    $query->bindParam(":id"$id); 
        
    $query->execute();
        while(
    $row $query->fetch(PDO::FETCH_ASSOC)){ 
            
    $employee_names[$row['name1']][$row['name2']][$row['name3']][$row['name4']][$row['name5']][] = $row['name6'];
        }    
                 
    }catch (
    PDOException $e){ 
        echo 
    "Database error: ".$e->getMessage(); 
    }  

      
        
    $data "";
        foreach(
    $employee_names as $k1 => $v1){
            
    $data .= "<div class=\"first\">\r";
            
    $data .= "$k1\r";
            foreach(
    $v1 as $k2 => $v2){
                if(!empty(
    $k2)):
                    
    $data .= "<div class=\"second\">\r";            
                    
    $data .= "$k2\r";
                        foreach(
    $v2 as $k3 => $v3){
                            if(!empty(
    $k3)):
                                
    $data .= "<div class=\"third\">\r";            
                                
    $data .= "$k3\r";
                                    foreach(
    $v3 as $k4 => $v4){    
                                        if(!empty(
    $k4)):
                                            
    $data .= "<div class=\"forth\">\r";            
                                            
    $data .= "$k4\r";
                                            foreach(
    $v4 as $k5 => $v5){
                                                if(!empty(
    $k5)):
                                                    
    $data .= "<div class=\"fifth\">\r";            
                                                    
    $data .= "$k5\r";
                                                    
    $data .= "</div>\r";
                                                endif;
                                            }
                                            
    $data .= "</div>\r";
                                        endif;
                                    }
                                
    $data .= "</div>\r";
                            endif;
                        }
                    
    $data .= "</div>\r";
                endif;
            }
            
    $data .= "</div>\r";
        }
        echo 
    $data;
    ?>

  5. #5
    SitePoint Guru bronze trophy
    Join Date
    Feb 2013
    Posts
    772
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)
    I guess, that display is only five... One more loop using values instead of keys would do it.

  6. #6
    SitePoint Enthusiast
    Join Date
    Feb 2014
    Posts
    41
    Mentioned
    4 Post(s)
    Tagged
    0 Thread(s)
    If you want to go to an arbitrary level of nestedness you need to use recursion.

    PHP Code:
    <?php
    // I can't be bothered to set up a table to make sure this works
    // so I'm declaring the data here. This function creates a "record"
    function Employee($id$name$superiorId) {
       return array(
    'id' => $id'name' => $name'superiorId' => $superiorId); }

    // This is the equivalent of your query function
    function subordinatesOf($superiorId) {
       
    $employees = [ // This is your table
          
    Employee(1'Zim'null),
          
    Employee(2'Gir'1),
          
    Employee(3'Washing machine'1),
          
    Employee(4'Dib'null),
          
    Employee(5'Gaz'4),
          
    Employee(6'Cat'5) ];
       return 
    array_filter($employees, function($e) use($superiorId) {
          return 
    $e['superiorId'] === $superiorId; }); }

    function 
    esc($str) {
       return 
    htmlSpecialChars($strENT_QUOTES'UTF-8'); }

    function 
    intAsEngOrdinal($n) {
       
    $ordinals = ['zeroth''first''second''third''fourth'];
       return 
    $ordinals[$n]; }

    // Produces HTML for all subordinates of $superiorId, performing a
    // query in the process
    function subordinatesHtml($superiorId$lvl 1) {
       return 
    implode(array_map(function($sub) use($lvl) {
          return 
    employeeHtml($sub$lvl); }, subordinatesOf($superiorId))); }

    // Produces HTML for a single employee and its subordinates
    function employeeHtml($employee$lvl 1) {
       return 
    "\n<div class='" intAsEngOrdinal($lvl) . "'>"
            
    '<strong>' esc($employee['name']) . '</strong>'
            
    subordinatesHtml($employee['id'], $lvl 1)
            . 
    '</div>'; }

    echo 
    '<style>div { margin-left: 20px; }</style>';
    echo 
    subordinatesHtml(null);
    The recursion in that example comes by the fact that subordinatesHtml() calls employeeHtml() that in turn calls subordinatesHtml() again but only if subordinatesOf() returns something other than an empty array, which is important because otherwise it would loop indefinitely.

    I’ve used a couple of higher-order functions (array_map() and array_filter()) in there but you could have used loops instead.

    Output:
    Code:
    <style>div { margin-left: 20px; }</style>
    <div class='first'><strong>Zim</strong>
    <div class='second'><strong>Gir</strong></div>
    <div class='second'><strong>Washing machine</strong>
    <div class='third'><strong>Gaz</strong></div></div></div>
    <div class='first'><strong>Dib</strong>
    <div class='second'><strong>Cat</strong></div></div>
    Last edited by parallelist; Feb 25, 2014 at 14:18. Reason: Added output

  7. #7
    SitePoint Enthusiast
    Join Date
    Feb 2014
    Posts
    41
    Mentioned
    4 Post(s)
    Tagged
    0 Thread(s)
    Bit of continuation...

    The major downside with what I initially suggested there is that a query is made for every single item in the hierarchy (tree in computer science terms). The simplest solution to query the whole table and store it all in a PHP array and then you can use the code I have written almost exactly as it is and then there’s only one query

    If you want to go to an arbitrary level of nestedness you need to use recursion.
    I’ve just realized I was completely wrong about this. Recursion is by far the simplest way but you can use loops you just have to figure out how each loop needs to be parameterized. I’ll come back with some code in a bit.

  8. #8
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,194
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)
    My algorithm in this post provides a means of building the hierarchy using a single query. The entire tree is selected than php is used to order the tree using recursion.
    The only code I hate more than my own is everyone else's.

  9. #9
    SitePoint Enthusiast
    Join Date
    Feb 2014
    Posts
    41
    Mentioned
    4 Post(s)
    Tagged
    0 Thread(s)
    Ah, thank you @oddz for that. I didnít know about the term adjacency list. I will be using that from now on.

    OK so hereís the code I promised showing that this can be done without recursion. Iíve actually gone one step furtheróor perhaps stupideróand done it without using references as well because I wanted to see if it was possible to just using array indexes. Iím happy to say it is.

    One of the advantages with the recursion method if you can just put in whatever decoration (bits of strings) you want to appear between the various calls and have it appear within the structure roughly as you would expect. When you use a loop you loose that advantage because the structure of the tree isnít visually reflected in the code itís only reflected through the logic you use. As a partial solution to that problem I generalized the loop a bit so that you can supply various parameters that will control how the tree will appear. Unfortunately it is still quite limited: I can think of a number of things you might want to do for which you would have to modify the core loop but the same problem is true of recursion too itís just that reworking the recursion isnít so difficult (if youíre used to it).

    PHP Code:
    <?php
    function Employee($id$name$supId) {
       return array(
    'id' => $id'name' => $name'supId' => $supId); }

    // This is the equivalent of your query function
    function subsOf($supNode) {
       
    $employees = [ // This is your table
          
    Employee(1'Zim'null),
          
    Employee(2'Gir'1),
          
    Employee(3'Wash'1),
          
    Employee(4'Dib'null),
          
    Employee(5'Gaz'4),
          
    Employee(6'Cat'5) ];
       
    // `array_values()` required to ensure contiguous seq for `renderNodes()`
       
    return array_values(array_filter($employees, function($node) use($supNode) {
          return 
    $node['supId'] === $supNode['id']; })); }

    function 
    esc($str) {
       return 
    htmlSpecialChars($strENT_QUOTES'UTF-8'); }

    function 
    intAsEngOrdinal($n) {
       
    $ordinals = ['zeroth''first''second''third''fourth'];
       return 
    $ordinals[$n]; }

    // Function to render nodes. They can be structured however you like as long as
    // you can define a `$getSubsF`. Miniglossary:
    //  - subs :: sub nodes
    //  - seq :: contiguously indexed array
    //  - f :: function
    function renderNodes(
       
    $rootNodes// seq of one or more root nodes
       
    $renderRoots// boolean whether to render the root nodes
       
    $getSubsF// returning a list of subs for a node
       
    $renderNodeF// returning str from node and depth level (int)
       
    $postSubsStr// str to insert after subs
       
    $betweenSiblingsStr // str to insert between siblings
    ) {
       
    $nodeSets = [$rootNodes];
       
    $xs = [$y 0];
       
    $output '';
       for (;;) {
          
    $x $xs[$y];
          if (
    $nodeSets[$y] === [] || !isSet($nodeSets[$y][$x])) {
             if (
    $y === 0) { break; }
             if (
    $y || $renderRoots) { $output .= $postSubsStr; }
             
    $y--;
             continue; }
          
    $node $nodeSets[$y][$x];
          if (
    $y || $renderRoots) {
             if (
    $x 0) { $output .= $betweenSiblingsStr; }
             
    $output .= $renderNodeF($node$y); }
          
    $nodeSets[$y 1] = $getSubsF($node);
          if (isSet(
    $xs[$y])) { $xs[$y]++; }
          else { 
    $xs[$y] = 1; }
          
    $y++;
          
    $xs[$y] = 0; }
       return 
    $output; }   

    // Two examples of usage:

    function employeeHtml($e$lvl) {
       return 
    "\n" str_repeat(' ', ($lvl 1) * 2)
            . 
    "<div class='" intAsEngOrdinal($lvl) . "'>"
            
    '<strong>' esc($e['name']) . '</strong>'; }

    function 
    employeeTxt($e) { return $e['name'] . '['; }

    $rootNodes = [Employee(null'(root)'null)];

    echo 
    renderNodes($rootNodesfalse'subsOf''employeeHtml''</div>''');
    echo 
    "\n\n";
    echo 
    renderNodes($rootNodesfalse'subsOf''employeeTxt'']'',');
    echo 
    "\n";
    Output:
    Code:
    <div class='first'><strong>Zim</strong>
      <div class='second'><strong>Gir</strong></div>
      <div class='second'><strong>Wash</strong></div></div>
    <div class='first'><strong>Dib</strong>
      <div class='second'><strong>Gaz</strong>
        <div class='third'><strong>Cat</strong></div></div></div></div>
    
    Zim[Gir[],Wash[]],Dib[Gaz[Cat[]]]]
    I was going to write a version of this that uses references instead of array indexes but Iíve already spent way too long on this.

  10. #10
    SitePoint Enthusiast
    Join Date
    Jun 2011
    Posts
    26
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks for all the replies, im probably going to use a modified version of what parellelist coded out. Thanks so much for the different perspectives on this issue guys!


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
  •