1

I am trying to create an expandable list. It is showing correctly for 1 node in the following manner:

  • University
    • Department0
      • Course0 Student0

However, when I do the same for more than 1 items ie. when number of departments are more than 1. It distributes the list anywhere around. Here is my code:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>Family Tree</title>
        <link rel="stylesheet" href="css/style.css" type="text/css" media="screen, projection">
        <script type="text/javascript" src="js/jquery-1.4.2.min.js">
        </script>
        <script type="text/javascript" src="js/scripts.js">
        </script>
        </script>
    </head>
    <body>

    <h1><b>Demo ExpandableList</b></h1>
    <div id="listContainer">
        <div class="listControl">
            <a id="expandList">Expand All</a>
            <a id="collapseList">Collapse All</a>
        </div>

        <ul id="expList">

        <?php                

            echo '<li>';
            echo 'University';

            for($department = 0; $department < 3; $department++){


                    if($department == 0){
                        echo '<ul>';
                        echo '<li>';
                        echo "d".$department;
                    }
                    else if($department == $countOfDepartments - 1){

                        echo '</li>';
                        echo '</ul>';
                    } else{

                        echo '<li>';
                        echo "d".$department;
                    }

                    for($course = 0; $course < 3; $course++){

                            if($course == 0){
                                echo '<ul>';
                                echo '<li>';
                                echo "c".$course;
                            }
                            else if($course == $countOfCourses - 1){

                                echo '</li>';
                                echo '</ul>';
                            } else{

                                echo '<li>';
                                echo "c".$course;
                            }

                            for($student = 0; $student < 3; $student++){
                                    if($student == 0){
                                        echo '<ul>';
                                        echo '<li>';
                                        echo "s".$student;
                                    }
                                    else if($student == $countOfStudents - 1){

                                        echo '</li>';
                                        echo '</ul>';
                                    } else{

                                        echo '<li>';
                                        echo "s".$student;
                                    }
                            } // for - 3
                    } // for - 2
            }// for - 1
            echo '</li>';

        ?>  
        </ul>
        </div>
    </body>
</html>

styles.js:

function prepareList() {
    $('#expList').find('li:has(ul)')
    .click( function(event) {
        if (this == event.target) {
            $(this).toggleClass('expanded');
            $(this).children('ul').toggle('medium');
        }
        return false;
    })
    .addClass('collapsed')
    .children('ul').hide();

    //Create the button funtionality
    $('#expandList')
        .unbind('click')
        .click( function() {
            $('.collapsed').addClass('expanded');
            $('.collapsed').children().show('medium');
    })

    $('#collapseList')
        .unbind('click')
        .click( function() {
            $('.collapsed').removeClass('expanded');
            $('.collapsed').children().hide('medium');
    })

};

$(document).ready( function() {
    prepareList()
});

style.css:

body {
    font-size: 16px;
}

#menu {
    list-style: none;
    padding: 0;
    margin: 0;
}

.clear {
    clear: both;
}


/********************/
/* EXPANDABLE LIST  */
/********************/
#listContainer{
  margin-top:15px;
}

#expList ul, li {
    list-style: none;
    margin:0;
    padding:0;
    cursor: pointer;
}
#expList p {
    margin:0;
    display:block;
}
#expList p:hover {
    background-color:#121212;
}
#expList li {
    line-height:140%;
    text-indent:0px;
    background-position: 1px 8px;
    padding-left: 20px;
    background-repeat: no-repeat;
}

/* Collapsed state for list element */
#expList .collapsed {
    background-image: url(../img/collapsed.png);
}
/* Expanded state for list element
/* NOTE: This class must be located UNDER the collapsed one */
#expList .expanded {
    background-image: url(../img/expanded.png);
}
#expList {
    clear: both;
}

.listControl{
  margin-bottom: 15px;
}
.listControl a {
    border: 1px solid #555555;
    color: #555555;
    cursor: pointer;
    height: 1.5em;
    line-height: 1.5em;
    margin-right: 5px;
    padding: 4px 10px;
}
.listControl a:hover {
    color:#222222; 
    font-weight:normal;
}

The list that I am trying to get is in the following format;

  • Unversity
    • Department0 - Course0 - Student0 - Student1 - Student2
    • Department1 - Course1 - Student0 - Student1 - Student2
    • Department2 - Course2 - Student0 - Student1 - Student2

What am I possibly doing wrong? My logic seems to be correct.

Nevermore
  • 882
  • 2
  • 18
  • 44

1 Answers1

1

Broken code: PHP errors

Your code fires errors like

"Notice: Undefined variable: countOfStudents"

(same with countOfCourses and countOfDepartments) because you didn't define these variables:

for ($department = 0; $department < 3; $department++) {
  if ($department == 0) {
    //...
  else if ($department == $countOfDepartments - 1) {

Instead it should be something like this (assuming $countOfDepartments is previously defined):

for ($department = 0; $department < $countOfDepartments; $department++) {
  if ($department == 0) {
    //...
  else if ($department == $countOfDepartments - 1) {

Code not working as expected

For now if we merely replace the undefined variables by 3, now it works without error, but it lacks opening last occurrence of departments, courses, and students, like this:

enter image description here

As illustrated below for departments, your code successively:

  • opens <ul> for university, then <li> for department #0:

    if ($department == 0) {
      echo '<ul>';
      echo '<li>';
      echo "d".$department;
    }
    
  • closes <li> for the (last) department #2, then <ul> for university:

    else if ($department == 3 - 1) {
      echo '</li>';
      echo '</ul>';
    
  • opens <li> for any other department (here #1):

    } else {
      echo '<li>';
      echo "d".$department;
    }
    

You can notice that it lacks to open last department and last course, and doesn't display last student, not to mention close all departments but last one (fortunately the browser manages that).

How to make it work

So the above was for explaining why you don't get what you're expecting. But I'll not propose you a way to correct your solution, since it is much too complicated and not the most efficient one.
Instead you might use a recursive method, like this:

function array2ul($array) {
  $return = NULL;
  foreach ($array as $key => $value) {
    $return .=
      '<li>' . $key . (is_array($value) ? array2ul($value) : $value) . '</li>';
  }
  return '<ul>' . $return . '</ul>';
}

Then the whole university <li> becomes:

echo array2ul([
  'd0' => [
    'c0' => ['s0' => NULL, 's1' => NULL, 's2' => NULL,],
    'c1' => ['s0' => NULL, 's1' => NULL, 's2' => NULL,],
    'c2' => ['s0' => NULL, 's1' => NULL, 's2' => NULL,],
  ],
  'd1' => [
    'c0' => ['s0' => NULL, 's1' => NULL, 's2' => NULL,],
    'c1' => ['s0' => NULL, 's1' => NULL, 's2' => NULL,],
    'c2' => ['s0' => NULL, 's1' => NULL, 's2' => NULL,],
  ],
  'd2' => [
    'c0' => ['s0' => NULL, 's1' => NULL, 's2' => NULL,],
    'c1' => ['s0' => NULL, 's1' => NULL, 's2' => NULL,],
    'c2' => ['s0' => NULL, 's1' => NULL, 's2' => NULL,],
  ],
]);

At this stage you might say that this solution uses a very simple function but needs a somewhat heavy previous definition!
It's because I guess the case you provided is intended only as an example, while for your real application you'll get source data from a database, so you'll have to adapt the source data (and maybe the function) to the real structure you get.

Anyway, at least for fun, let's turn the current example into a yet simple form. You can generate the above structure from the mere expression of your needs, like this:

function generate($groups) {
  $name = key($groups);
  $number = array_shift($groups);
  for ($i = 0; $i < $number; $i++) {
    $out[$name . $i] = $groups ? generate($groups) : NULL;
  }
  return $out;
}

Then the whole university <li> becomes merely:

echo array2ul(generate(['d' => 3, 'c' => 3, 's' => 3]));

And finally here is your entire HTML code:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <title>Family Tree</title>
      <link rel="stylesheet" href="css/style.css" type="text/css" media="screen, projection">
      <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
      <script type="text/javascript" src="js/scripts.js"></script>
  </head>
  <body>

  <h1><b>Demo ExpandableList</b></h1>
    <div id="listContainer">
      <div class="listControl">
        <a id="expandList">Expand All</a>
        <a id="collapseList">Collapse All</a>
      </div>

      <ul id="expList">
        <li>
        University
<?php echo array2ul(generate(['d' => 3, 'c' => 3, 's' => 3])); ?>  
        </li>
      </ul>
    </div>
  </body>
</html>

<?php
function array2ul($array) {
  $return = NULL;
  foreach ($array as $key => $value) {
    $return .=
      '<li>' . $key . (is_array($value) ? array2ul($value) : $value) . '</li>';
  }
  return '<ul>' . $return . '</ul>';
}

function generate($groups) {
  $name = key($groups);
  $number = array_shift($groups);
  for ($i = 0; $i < $number; $i++) {
    $out[$name . $i] = $groups ? generate($groups) : NULL;
  }
  return $out;
}

It now gives the expected resul:

enter image description here

cFreed
  • 4,404
  • 1
  • 23
  • 33
  • I agree with your answer. However, I had completely forgotten to mention that; I was taking the data from my data-source and later for the sake of simplicity; I mistakenly left out those undeclared variables. Thanks for pointing out my mistake. However, the $group over here is undefined. – Nevermore Jan 31 '16 at 17:46
  • One more question; I was considering to form a very large JSON string out of all the data in my data-source but what if my data-source contains very large amount of data (say 20000 records for all). It will take a lot of time. Any thoughts on that? – Nevermore Jan 31 '16 at 17:47
  • @Nevermore I don't understand what you mean with "the $group over here is undefined". Regarding your question about JSON data as source: 1) For a so large amount of data, sure that the performance aspect becomes important: as a 1st thought you might consider moving HTML generation to client side with JS, and display progressively to avoid blank page for a long time; 2) Anyway, could you post a example of the JSON structure? – cFreed Jan 31 '16 at 20:20
  • No issue about $group. I got that. 1. Thats a good idea to move HTML generation to client-side. – Nevermore Feb 01 '16 at 05:35
  • { "University": { "Department": [ { "department_name": "Computers", "course": [ { "course_name": "Networking", "students": [ "Jack", "Jill" ] } ] }, { "department_name": "Arts", "course": [ { "course_name": "Painting", "students": [ "Lee", "Chang" ] } ] }, } – Nevermore Feb 01 '16 at 06:18
  • @Nevermore This is a quite different structure than in your example, but sure the code I proposed can be adapted to it, with PHP as well as JS. Nevertheless I'm tempted to consider an alternative (better, IMO) solution which needs the structure to be slightly different (because I find it currently not very consistent). So the question: Have you control about the JSON structure? – cFreed Feb 01 '16 at 20:35
  • The JSON structure is exactly the same as the structure I had asked for (in the question). Yes it can be adapted to the data-source still I needed some hint about it. – Nevermore Feb 01 '16 at 20:39
  • 1
    @Nevermore "The JSON structure is exactly the same": you mean that the _hierarchy_ is the same, and I agree. But not the _structure_, strictly speaking. It's why I asked. Since you confirm you can modify it, I'd suggest to make it more consistent: 1) currently university is an object, should be an array of objects, like all other levels are ; 2) `students` is a good key, should have the equivalent for `courses` and so on; 3) each object should have a `name` key, rather than `course_name` and so on. This way it'd be easy to keep a simple and general-use function to generate HTML. – cFreed Feb 01 '16 at 22:16
  • I agree with you. I have names for each entity in my data-source for course, student and department. I do not understand you saying "confirm if you can modify it". I can modify it anytime. You are correct on your points 1, 2 & 3. I am planning to get the entries from data-source and arrange them according to their respective departments, courses and student. – Nevermore Feb 02 '16 at 04:19
  • 1
    @Nevermore You didn't understand me saying "confirm if you can modify": it's because, as a not-native English speaker, I probably badly expressed me. Let me try better... A) In a previous comment, I said that the source structure could be be improved, but I didn't know if it was under your control. B) You answered "yes". C) Because you answered "yes", you _confirmed you could modify it_ (the structure), so I then exposed the improvements I thought to. – cFreed Feb 02 '16 at 11:43
  • I will try to modify it man. Thanks for the help. :-) You can check this link http://code.stephenmorley.org/javascript/collapsible-lists/ – Nevermore Feb 02 '16 at 17:02
  • 1
    @Nevermore Glad to help. In the other hand, thanks for the link: intersting! – cFreed Feb 02 '16 at 19:11