PHP - Simple Nested Unordered List (UL) Array
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
* To开发者_高级运维pic 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>
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>';
}
精彩评论