I have a categories table that looks like this:
----------------------------------------
| id | parentId | Name |
----------------------------------------
1 0 Cat 1
2 0 Cat 2
3 0 Cat 3
4 2 Cat 4
5 3 Cat 5
6 5 Cat 6
Basically I need to iterate through the categorys creating a UL LI html list like the following:
<ul id="categories">
<li id="1">Cat 1</li>
<li id="2">Cat 2
<ul>
<li id="4">Cat 4</li>
</ul>
</li>
<li id="3">Cat 3
<ul>
<li id="5">Cat 5
<ul>
<li id="6">Cat 6</li>
</ul>
</li>
</ul>
</li>
</ul>
Im having major issues trying to iterate over this trying to create the above html. The id's maybe any number of levels deep within parentId's. Im donig this in PHP. Because there are nth number of levels deep I think I need to do some kind of array_walk funuction but not surch how. Also to make things just a little harder the machine it sists on runs PHP4 and I know it needs upgrading but it cant at the min so I need a php 4 solution ideally. How should I go about doing this?
Try the left/right tree method for storing hierarchical information in a database.
http://blogs.sitepoint.com/hierarchical-data-database/
This is what I do in my website, where I have multi-level LIs that need to open at 1:6 and have children 2:3,4:5 where the first number is the 'left' and the second is the 'right'. I have about 5 levels deep at the moment, but you could have many more. It's just a matter of developing an interface for setting the correct left/right values based on the position you add it to.
You just have to add a 'lft' and 'rgt' column to your table (as explained in that article).
First create a tree structure and insert your categories into the tree using id and parent_id. Then try Depth-first_search using either a list of references to arrays to be processed or recursion.
function printRecList($tree){
// exit condition
if (is_string($tree))
echo "<li>$tree</li>";
echo "<ul>";
foreach ($tree as $subtree)
printRecList($subtree); // recursion step
echo "</ul>";
}
The way your database is structured, you cannot do that with a single mysql query and you should do it recursively. Something in line with:
function print_children ($id) {
$children = query("SELECT * FROM `table` WHERE `parentId` = " . (int)$id);
if (!empty($children)) {
echo '<ul>';
foreach ($children as $child) {
echo '<li>' . $child['name'];
print_children($child['id']);
echo '</li>';
}
echo '</ul>';
}
}
print_children(0);
Replace query with something that gets results for your database query.
function writelevel($id, $txt, $children) {
if (isset($txt[$id]))
echo "<li id=\"$id\">".$txt[$id];
if (isset($children[$id])) {
echo "<ul>";
foreach ($children[$id] as $child)
writelevel($child, $txt, $children);
echo "</ul>";
}
if (isset($txt[$id]))
echo "</li>";
}
//Assuming your query is done and the result is in $qry
$txt=array();
$children=array();
//Fetch and structure data
while (true) {
//Fetch next row
$row=mysql_fetch_row($qry);
if (!$row) break;
//Store text
$txt[$row[0]]=$row[2];
//Store child relationships
if (!isset($children[$row[1]])) $children[$row[1]]=array();
$children[$row[1]]=$row[0];
}
//Writeout
writelevel(0);
Related
I'm having doubts about manipulating the child menu items to improve the display style.
I am currently generating according to documentation:
function index()
{
$list = $this->Categorias->find('threaded')->toArray();
$this->set('list', $list);
}
$renderItems = function($items) use (&$renderItems)
{
echo '<ul>';
foreach ($items as $item) {
echo '<li>';
echo h($item->name);
if ($item->children) {
$renderItems($item->children); // < recursion
}
echo '</li>';
}
echo '</ul>';
};
$renderItems($list);
But the display is in error.. The display is in this format:
I appreciate any comments!
As mentioned in the tree behavior docs, if you want to use threaded results, then you need some kind of recursive functionality to iterate not only over the top level items (which your example is doing), but also over the nested child items.
Also note that using the children finder as in your example, will only retrieve the children of the node with the specified primary key, ie it won't retrieve their parent node, also if the table contains multiple root nodes, they wouldn't be retrieved either. So depending on your data you might need to use only the threaded finder.
That being said, the children of an item are nested under the children property/key, so a basic example could look like this:
$renderItems = function($items) use (&$renderItems)
{
echo '<ul>';
foreach ($items as $item) {
echo '<li>';
echo h($item->name);
if ($item->children) {
$renderItems($item->children); // < recursion
}
echo '</li>';
}
echo '</ul>';
};
$renderItems($list);
That would create a nested list like this (without the formatting/indenting of course):
<ul>
<li>
Fun
<ul>
<li>
Sport
<ul>
<li>Surfing</li>
<li>Skating</li>
</ul>
</li>
</ul>
</li>
<li>
Trips
<ul>
<li>National</li>
<li>International</li>
</ul>
</li>
</ul>
See also
Cookbook > Database Access & ORM > Behaviors > Tree
Just getting into the whole MVC developing style.
Using CI I think I got the initial hang on the controllers, models and views. But, I run into a problem which was an easy-peasy for me in "my own" way of coding.
I have a single DB table to drive dynamic menu. The design is:
With sample data being:
The logic, in my head anyway, would be:
select * where active=1, then
if row's parent_id==0 check the retrieved data-set for rows where
parent_id==id of the current row
if not found, generate <li class="no_subs"></li> element
if found, generate <li class="subs"> element, open new <ul>
generate <li></li> for all items that matched, close <ul>, close
"subs" <li>
So, based on the above sample data the resulting html would be:
<li class="no_subs">Home</li>
<li class="no_subs">News</li>
<li class="subs">Training
<ul>
<li>Materials</li>
<li>Tests</li>
</ul>
</li>
<li class="subs">Other
<ul>
<li>Usefull links</li>
</ul>
</li>
So far all I can do is generate the top level menu.
Any ideas how to tackle this?
Thanks,
You can join the same table twice to achieve your goal
grub the data where parent_id == 0 and active == 1, so you will get the only menu titles.
join the data with the same table where id == parent_id. so you will get the this table.
id | name | parent_id | active | link | as_id | as_name | as_parent_id | as_active | as_link
**this 2 steps can be done in single sql query.
then i think this code will help you...
$sub_menu_started = false;
for ($i=0; $i < count($result); $i++) {
if (empty($result[$i]['as_name'])) {
echo '<li class="no_subs">'.$result[$i]['name'].'</li>';
}
else {
if(!$sub_menu_started) {
echo '<li class="subs">'.$result[$i]['name'];
echo '<ul><li>'.$result[$i]['as_name'].'</li>';
$sub_menu_started = true;
}
else {
if ($result[$i]['id'] == $result[$i-1]['id']) {
echo '<li>'.$result[$i]['as_name'].'</li>';
if($result[$i]['id'] != $result[$i+1]['id']) {
echo '</ul></li>';
$sub_menu_started = false;
}
}
}
}
}
if ($sub_menu_started) {
echo '</ul></li>';
}
I have a code like this.
<ul>
<?php foreach (getUserMainMenus($get_user_id) as $get_main_menu): ?>
<li class='has-sub'><a href='#'><span><?=$get_main_menu['menu_name']; ?></span></a>
<ul>
<?php foreach (getUserChildMenu($get_main_menu['m_id'], $get_user_id) as $sub_menu): ?>
<li class='has-sub'><a href='<?=$sub_menu['menu_url']; ?>'><span><?=$sub_menu['menu_name']; ?></span></a>
<ul>
<?php foreach (getUserSubChildMenu($sub_menu['m_id'], $get_user_id) as $sub_sub_menu): ?>
<li class='has-sub'><a href='<?=$sub_sub_menu['menu_url']; ?>'><span><?=$sub_sub_menu['menu_name']; ?></span></a></li>
<?php endforeach; ?>
</ul>
</li>
<?php endforeach; ?>
</ul>
</li>
<?php endforeach; ?>
</ul>
And the result set for the second foreach is like this.
Array ( [m_id] => 9 [menu_name] => MenuName [parent_menu_id] => 4 [menu_order] => 1 [menu_url] => menu1.php [status] => 1 )
How to check a if condition after all foreach statement.
In other words, i need to get the $sub_menu values and if there is values show the li tag.
Also - I need to check the $sub_sub_menu variable, and if there is values show the li tag.
How can i achieve that?
Thanks,
Kimz
I guess you try to create a navigation menu. Where the sub entries should only appear when the top menu item is selected by the visitor of your page.
Is that right?
Ok if so. You might have in mind that.
if a user displays your page first. you might show only the top menu items.
if then a user selects one of the top menu items he/she clicks on a link an that reloads your script with some additional information.
Now your script needs to figure out which top menu item the user selected based on the additional information.
Depending on the selection of the user you might show or hide submenu items.
What your job here is, you have to make sure that your script detects which top-menu item is clicked at.
Do you need more help, or is it clear what to do?
Ok how about this as an basic example for dynamic php menus as test.php
<?php
$menu="";
extract( $_GET, EXTR_PREFIX_ALL, "url" );
if (isset($url_menu)){
$menu=$url_menu;
echo "you selected ".$menu."<br>";
}
echo "<ul>";
// top menu 1
echo '<li>Top1';
if ($menu=="top1"){
echo "<ul>";
echo "<li>Submenu</li>";
echo "</ul>";
}
echo "</li>";
// top menu 2
echo '<li>Top2';
if ($menu=="top2"){
echo "<ul>";
echo "<li>Submenu</li>";
echo "</ul>";
}
echo "</li>";
echo "</ul>";
?>
See any top menu item hands over the additional variable "menu". This is either "top1" or "top2" in this case. Now your script on reload checks whether "menu" is already set and depending on the value of "menu" it shows the corresponding sub menu.
There is still a long way to go, because in my case I use fixed menu items where in your case you load the menu items depending on the "userid".
Let me know if the example above works at your place and if you need additional support to adopt it to your dynamically loaded menus.
Following that idea you need to replace
<li class='has-sub'><a href='#'><span><?=$get_main_menu['menu_name']; ?></span></a>
by adding for example the variable name "level0"
<li class='has-sub'><a href='<?= ?level0=$sub_menu['menu_name']; ?>'><span><? $get_main_menu['menu_name']; ?></span></a>
then you can check in you sub menu if "level0" is set as you expect it and then show or hide the sub menu items.
I'm listing some movies in my web so the user can choose the best movie.
I’m classifying them by genre.
But the lists under each genre are too long and I want to make two columns of titles.
The number of movies in each column will depend of the genre.
So my code is like this now:
$i = 1;
echo "<ul>";
foreach($arrayMovies as $k=>$v)
{
echo "<li><input type=\"checkbox\" id=\"flat-$i\" name=\"$genre-$i\" value=\"$k\">
<label for=\"flat-$i\">$v</label></li>";
$i++;
}
echo "</ul>";
This code is showing the long list, something like this for drama:
Forest Gump
The Hours
Mullholand Drive
Titanic
.
.
Let’s say that for the drama genre I need two titles per column:
<ul>
<li> Forest Gump</li>
<li> The Hours</li>
</ul>
<ul>
<li> Mullholand Drive</li>
<li> Titanic</li>
</ul>
How can achieve this with my code??
The second column I can achieve by using css, I just need a new <ul> after two titles.
Please notice that the number two in -two titles per column- is a dynamic number*
*I’ll count how many rows per title each genre has to get a total number and then I’ll do half of it to get the number of titles per column (the number of titles in each genre is even)
Thanks very much!
Just insert those UL tags at the half of the iterations:
$arrayMovies = array( 'Forest Gump', 'The Hours', 'Mullholand Drive', 'Titanic', 'The Intouchables' );
$arrayMoviesCount = count( $arrayMovies );
echo '<ul>';
for( $i = 0; $i < $arrayMoviesCount; $i++ )
{
if( ceil( $arrayMoviesCount / 2 ) == $i )
{
echo '</ul><ul>';
}
echo sprintf( '<li>%s</li>', $arrayMovies[$i] );
}
echo '</ul>';
Output of the above code:
<ul>
<li>Forest Gump</li>
<li>The Hours</li>
<li>Mullholand Drive</li>
</ul>
<ul>
<li>Titanic</li>
<li>The Intouchables</li>
</ul>
The ceil is required for lists with an odd number of entries; using ceil will result in the first list having one entry more. If the additional (not-even) entry should be displayed in the second list, you could replace ceil by floor.
Sorry for the previous answer, I didn't understand what you wanted to exactly do.
If the problem is: "I want to categorize movies", then use an associative array instead.
Else, if you just want to display two movies in two coloumns, I would suggest you this:
$i = 1;
$flagCounter = 0;
echo "<ul>";
foreach($arrayMovies as $k=>$v)
{
if ($flagCounter != 2) {
echo "<li><input type=\"checkbox\" id=\"flat-$i\" name=\"$genre-$i\" value=\"$k\">
<label for=\"flat-$i\">$v</label></li>";
$i++;
$flagCounter++;
}
else {
echo "</ul><ul>";
echo "<li><input type=\"checkbox\" id=\"flat-$i\" name=\"$genre-$i\" value=\"$k\">
<label for=\"flat-$i\">$v</label></li>";
$i++;
$flagCounter = 1;
}
}
echo "</ul>";
To be honest at all, I don't really trust this solution, this is just a workaround for your case. If you're interested in a better one, try using an associative array with a structure like such:
$yourArray = array ( "Category" => array ("movie1","movie2") [and so on...] );
In this way, you will be able to display everything more clearly, being able to access to both movies and categories!
Also, why did you choose to use a list instead of a table for displaying these?
And.. Where does the variable $genre come from? Is this all your code or what? If not, can you please provide us the structure of your array?
I thought to implement advanced commenting system in my website using PHP-MySql. I finally settled for a 3-level comment-reply system for this purpose.
Well, for that purpose I came across this article to implement the data-structure of the SQL database.
I planned to use nested set mdoel for the feature I want to use. The structure of the comments are like this-
<ul>
<li>Parent comment</li>
<ul>
<li>First reply of parent comment</li>
<ul>
<li>reply of the previous reply</li>
<ul>
<li>reply of the previous reply</li>
<li>another reply of the previous reply</li>
</ul>
<li>another reply of the previous comment</li>
</ul>
<li>second reply of the parent comment</li>
</ul>
</ul>
For this type of structure, I have been playing around with PHP to show the query detecting the parents and its child uniquely(for fetching user details associated with each comment) and produce the output in the manner if shown above. Do anyone have idea how to do it. Please help me out.
EDIT :
I have a seperate user table in SQL linked to the comment table as user.id=comment.id. So considering this, what would be the recommended approach to detect user activity for each comment? I mean, for ex- I want to fetch user name and email for a sub-comment of parent comment 2. Hoow could it be done?
Use the query from "Finding the Depth of the Nodes", and this PHP code will create the nested lists.
$cur_depth = -1;
while ($row = mysqli_fetch_assoc($query)) { // Loop through results of query
if ($row['depth'] > $cur_depth) {
echo "<ul>\n";
$cur_depth = $row['depth'];
}
else while ($cur_depth > $row['depth']) {
echo "</ul>\n";
$cur_depth--;
}
echo "<li>" . $row['comment'] . "</li>\n";
}
while ($cur_depth > -1) {
echo "</ul>\n";
$cur_depth--;
}