4

I have seen a few variants of this nested UL array question on stackoverflow, but I think mine is simpler than the others. I am looking for a simple array loop that allows allows for an infinite amount of topics (parents) with an infinite amount of items (children), like:

<ul>
    <li>Topic</li>
        <ul>
            <li>Item</li>
            <li>Item</li>
            <li>Item</li>
            <li>Item</li>
        </ul>
</ul>

I have attempted this with the following code:

<?php
$result = mysql_query("SELECT * FROM News");

$topicname = false;

while($row = mysql_fetch_array($result)) {
    if (!$row['TopicID']) {
            $row['TopicName'] = 'Sort Me';
    }
    if ($topicname != $row['TopicName']) {
        echo '<ul><li>' . $row['TopicName'] . '</li><ul>';
        $topicname = $row['TopicName'];
    }
    echo '';
    echo '<li>' . $row['NewsID'] . '"</li>';
    echo '';
}
if ($topicname != $row['TopicName']) {
    echo '</ul>';
    $topicname = $row['TopicName'];
}   
?>

The above code renders the following:

* Topic A
      o News 1
      o News ...
      o News 51000
            + Topic B
                  # News 1
                  # News ...
                  # News 51000
                        * Topic C
                              o News 1
                              o News ...
                              o News 51000
                                    + Topic D
                                          # News 1
                                          # News ...
                                          # News 51000

The would like the code to render the following:

* Topic A
      o News 1
      o News ...
      o News 51000
* Topic B
      o News 1
      o News ...
      o News 51000
* Topic C
      o News 1
      o News ...
      o News 51000
* Topic D
      o News 1
      o News ...
      o News 51000

Any ideas would be greatly appreciated!

QUESTION SOLVED BY MARK; possible to solve this related question?

Hi Mark: Yes, this did the trick! Very helpful, thank you. I was wondering if you might be able to help me take this to another level of sophistication. If you think it is not appopriate to ask within this question, let me know and I will ask separately, but your code is solid, so I thought I would follow up with it.

Using the same code above, I am hoping to give the user the option to view the data via the choice of 1 column, 2 columns, 3 columns, 4 columns, 5 columns, etc. (up to 10). The rows of data would be divided into separate DIV tags and the row count would include both the topics and the news items. I will control the DIV tags with my CSS, but I would like to group the row count evenly into DIV tags for the specified amount of columns. I would like the children news items to not be separated from their parent and the groups to be as even as possible. If there is a breaking point where 1 column can be longer than the other and it's even/arbitrary, precedence would go to the left-most column, like: this mini-illustration:

XXX
XX
X

I don't know how clear this is, so here is an example. If the user chooses 1 column, they would see the following 30 "rows" of data:

<div id="Columns1Group1of1">
* Topic A
      o News 1
      o News 2
      o News 3
* Topic B
      o News 1
      o News 2
      o News 3
      o News 4
* Topic C
      o News 1
      o News 2
      o News 3
      o News 4
      o News 5
* Topic D
      o News 1
      o News 2
      o News 3
* Topic E
      o News 1
      o News 2
      o News 3
      o News 4
* Topic F
      o News 1
      o News 2
      o News 3
      o News 4
      o News 5
</div>

If the user chooses 2 columns, they would see the following 30 "rows" of data divided into 2 groups with DIV tags wrapped around each. This happens to space out nicely by coincidence:

<div id="Columns2Group1of2">        <div id="Columns2Group2of2">
* Topic A                           * Topic D
      o News 1                          o News 1
      o News 2                          o News 2
      o News 3                          o News 3
* Topic B                           * Topic E
      o News 1                          o News 1
      o News 2                          o News 2
      o News 3                          o News 3
      o News 4                          o News 4
* Topic C                           * Topic F
      o News 1                          o News 1
      o News 2                          o News 2
      o News 3                          o News 3
      o News 4                          o News 4
      o News 5                          o News 5
</div>                              </div>

If the user chooses 3 columns, they would see the following 30 "rows" of data divided into 3 groups with DIV tags wrapped around each. The spacing is starting to get tricky and I am open to suggestions.

<div id="Columns3Group1of3">        <div id="Columns3Group2of3">        <div id="Columns3Group3of3">
* Topic A                           * Topic C                           * Topic E
      o News 1                          o News 1                            o News 1
      o News 2                          o News 2                            o News 2
      o News 3                          o News 3                            o News 3
* Topic B                               o News 4                            o News 4
      o News 1                          o News 5                        * Topic F
      o News 2                      * Topic D                               o News 1
      o News 3                          o News 1                            o News 2
      o News 4                          o News 2                            o News 3
</div>                                  o News 3                            o News 4
                                    </div>                                  o News 5
                                                                        </div>

If the user chooses 4 columns, they would see the following 30 "rows" of data divided into 4 groups with DIV tags wrapped around each. Again, I don't even know how to manually space it for my illustration, but it is important for the children to stay under the parent.

<div id="Columns4Group1of4">        <div id="Columns4Group2of4">        <div id="Columns4Group3of4">        <div id="Columns4Group4of4">
* Topic A                           * Topic C                           * Topic D                           * Topic F
      o News 1                        o News 1                            o News 1                            o News 1
      o News 2                        o News 2                            o News 2                            o News 2
      o News 3                        o News 3                            o News 3                            o News 3
* Topic B                             o News 4                          * Topic E                             o News 4
      o News 1                        o News 5                            o News 1                            o News 5
      o News 2                      </div>                                o News 2                          </div>
      o News 3                                                            o News 3
      o News 4                                                            o News 4
</div>                                                                  </div>
1
  • 2
    your last close ul is outside the loop, move it inside Commented Mar 1, 2011 at 1:28

1 Answer 1

1

This should do the trick:

$result = mysql_query("SELECT * FROM News");
$topicname = '';

// open list of topics
echo '<ul>';

// loop through topics
while($row = mysql_fetch_array($result)) {
    if (!$row['TopicID']) {
        // fake topic name for unsorted stuff
        $row['TopicName'] = 'Sort Me';
    }
    if ($topicname != $row['TopicName']) {
        if($topicname != ''){
            // had a topic name, means we opened a list
            // that hasn't been closed, close it.
            echo '</ul>';
        }
        // print this topic and open the list of articles
        echo '<li>' . $row['TopicName'] . '</li><ul>';
        // update the current topic to be this TopicName
        $topicname = $row['TopicName']; 
    }
    // the news item
    echo '<li>' . $row['NewsID'] . '"</li>';
}
if($topicname != ''){
    // we saw at least one TopicName, we need to close
    // the last open list.
    echo '</ul>';
}
// end topic list
echo '</ul>';

I think your real problem is that you were opening two lists each time, but only closing one (even moving the last block inside the list).


For the second part of your (new) question:

I'll caveat that for larger lists (say, more than 300 items) the tradeoff I'm making with respect to storing the list in memory and iterating twice rather than just querying for counts needed would swing the other way. That is, the solution below puts everything into memory, then iterates a second time to print it out; an alternative would be to run two queries, one to find the number of unique TopicNames and one to find the number of total items in the list.

Also, for display, you really want to solve some optimization for layout, I'll do this naively and just make a (roughly) equal number of topics per column and when the division doesn't work out this will weight towards the left. You'll see where you can tweak or replace some code to get different (and better?) results.

$columns = // user specified;

$result = mysql_query("SELECT * FROM News");
$num_articles = 0;

// $dataset will contain array( 'Topic1' => array('News 1', 'News2'), ... )
$dataset = array();
while($row = mysql_fetch_array($result)) {
    if (!$row['TopicID']) {
        $row['TopicName'] = 'Sort Me';
    }
    $dataset[$row['TopicName']][] = $row['NewsID'];
    $num_articles++;
}

$num_topics = count($dataset);

// naive topics to column allocation
$topics_per_column = ceil($num_topics / $columns);

$i = 0; // keeps track of number of topics printed
$c = 1; // keeps track of columns printed
foreach($dataset as $topic => $items){
    if($i % $topics_per_columnn == 0){
        if($i > 0){
            echo '</ul></div>';
        }
        echo '<div class="Columns' . $columns . 'Group' . $c . '"><ul>';
        $c++;
    }
    echo '<li>' . $topic . '</li>';
    // this lists the articles under this topic
    echo '<ul>';
    foreach($items as $article){
        echo '<li>' . $article . '</li>';
    }
    echo '</ul>';
    $i++;
}
if($i > 0){
    // saw at least one topic, need to close the list.
    echo '</ul></div>';
}
Sign up to request clarification or add additional context in comments.

7 Comments

Hi Mark: Yes, this did the trick! Very helpful, thank you. I was wondering if you might be able to help me take this to another level of sophistication. If you think it is not appopriate to ask within this question, let me know and I will ask separately, but your code is solid, so I thought I would follow up with it. I have the question specifics above in the original post in a new section entitled, "QUESTION SOLVED BY MARK; possible to solve this related question?"
@webdude7: if there's any more major changes to the question please open a new question. also, if this solves your problem it's customary to upvote and/or accept the answer.
Hi Mark: Understood -- I will do this for future questions. For the above on my second (new) question, the code render wonderfully except the ($c+1) does not seem to be adding the +1 for me. For example, when I set the columns to 3, the output is <div class="Columns3Group1"> and <div class="Columns3Group1"> and <div class="Columns3Group1">.
Hi Mark: I marked your response as the accepted answer. If you might know of an easy fix for the ($c+1) issue, please let me know as it would prove greatly helpful. Thanks again for your assistance.
@webdude77: should be fixed now.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.