I want to need to make a menu where i don't know how deep the menu will be.
this is how my database table looks like:
table menu fields id, parent_id, name
i already tryed this to get a nice array back, but it dont work:
public function get()
{
$nodeList = array();
$tree = array();
$query = $this->db->get('menu');
$result = $query->result_array();
foreach ($result as $row) {
$nodeList[$row['id']] = array_merge($row, array('children' => array()));
}
foreach ($nodeList as $nodeId => &$node) {
if (!$node['parent'] || !array_key_exists($node['parent'], $nodeList)) {
$tree[] =& $node;
} else {
$nodeList[$node['parent']]['children'][] =& $node;
}
}
return $nodeList;
}
eventually I want to achieve this(a nice looking menu):
<ul>
<li><a href="">Apple<a/>
<ul>
<li>Iphone</li>
<ul>
<li>4S</li>
<li>5</li>
</ul>
<li>Ipad</li>
<ul>
<li>4</li>
<li>5</li>
</ul>
</ul>
</li>
<li><a href="">Samsung<a/>
<ul>
<li>Galaxy</li>
<ul>
<li>S3</li>
<li>S4</li>
</ul>
</ul>
</li>
I hope you understand my question and can help me. i REALLYY!! need this.
thanks ;)
Your get() should probably return $tree, not $nodeList. Other than that, it seems perfectly fine.
Update. This example shows how to build HTML for nested menu:
function getUl($nodes)
{
$result = "<ul>\n";
foreach ($nodes as $n) {
$result .= '<li>...';
if (!empty($n['children'])) {
$result .= getUl($n['children']);
}
$result .= "</li>\n";
}
$result .= "\n</ul>";
return $result;
}
Related
This class is designed to iterate over a WordPress menu structure (nested arrays/objects) to produce a finished menu. While the source of my data is WordPress, I feel this question belongs on SO instead of WP.SO because the issue is rooted more in PHP (applies to anyone attempting a recursion).
For some reason, I'm seeing duplicate results in the hierarchy. Also, I'm noticing that certain HTML elements are not closing properly. It seems like I've nested everything properly, but the result is what you see here.
To assist with debugging, I've added some * to impact the markup visually. Maybe you guys know something I don't. Fingers crossed and thanks in advance for your input!
My class
class Nav_Menu
{
public $wp_nav;
public $nested_nav;
public $recursion_depth = 0;
function __construct( $menu, $args = array() )
{
$format = new Format;
if( $menu )
{
$this->wp_nav = wp_get_nav_menu_items($menu, $args);
$this->nested_nav = $this->build_tree($this->wp_nav);
$output = $this->build_output($this->nested_nav);
$output_formatted = $format->HTML($output);
// echo $output;
echo $output_formatted;
}
}
private function build_output( $menu = array() )
{
$output = '**';
$output.= $this->recurse_menu($menu, $output);
return $output;
}
private function recurse_menu( $menu = array(), $output )
{
global $post;
if( !empty($menu) && !empty($output) )
{
$this->recursion_depth++;
// ul classes
$classes_ul = array();
$classes_ul[] = ( $this->recursion_depth > 1 ? 'sub-menu' : '' );
$classes_ul[] = 'depth-' . $this->recursion_depth;
// process list wrappers
$output.= '<ul class="' . $this->process_classes($classes_ul) . '">';
// loop through menu items
foreach( $menu as $menu_key => $menu_val )
{
// process list items
$output.= '<li>' . $menu_val->title;
// if necessary, handle children and recurse
if( !empty($menu_val->children) )
{
// recurse, and call this again
$output.= $this->recurse_menu($menu_val->children, $output);
}
// process list items
$output.= '</li>';
}
// process list wrappers
$output.= '</ul>';
}
return $output;
}
private function process_classes($classes = array())
{
if( !$classes )
{
return;
}
return trim(implode(' ', $classes));
}
private function build_tree( $elements = array(), $parent_id = 0 )
{
$branch = array();
foreach($elements as $element)
{
if ($element->menu_item_parent == $parent_id)
{
$children = $this->build_tree($elements, $element->ID);
if ($children)
{
$element->children = $children;
}
$branch[] = $element;
}
}
return $branch;
}
}
$mynav = new Nav_Menu('Test Menu');
The resulting output
****
<ul class="depth-1">
<li>
One**
<ul class="depth-1">
<li>
One
<ul class="sub-menu depth-2">
<li>
Sub One
</li>
<li>
Sub Two
</li>
<li>
Sub Three
</li>
</ul>
</li>
<li>
Two
</li>
<li>
Three**
<ul class="depth-1">
<li>
One**
<ul class="depth-1">
<li>
One
<ul class="sub-menu depth-2">
<li>
Sub One
</li>
<li>
Sub Two
</li>
<li>
Sub Three
</li>
</ul>
</li>
<li>
Two
</li>
<li>
Three
<ul class="sub-menu depth-3">
<li>
Sub One
</li>
<li>
Sub Two
</li>
</ul>
</li>
<li>
Four
</li>
</ul>
WordPress menu in the backend
If someone knows the reason why, I'd be willing to know, so I'll reserve choosing an answer for now. My guess is some kind of odd namespacing/scope issue for the variable $output. Who knows, I'm kinda tired right now.
The fix to get a legit structure was this...
Class
class Nav_Menu
{
public $wp_nav;
public $nested_nav;
public $recursion_depth = 0;
public $output = '';
function __construct( $menu, $args = array() )
{
$format = new Format;
if( $menu )
{
$this->wp_nav = wp_get_nav_menu_items($menu, $args);
$this->nested_nav = $this->build_tree($this->wp_nav);
$this->build_output($this->nested_nav);
$output_formatted = $format->HTML($this->output);
// echo $this->output;
echo $output_formatted;
}
}
private function build_output( $menu = array() )
{
$this->recurse_menu($menu);
}
private function recurse_menu( $menu = array() )
{
global $post;
if( !empty($menu) )
{
$this->recursion_depth++;
// ul classes
$classes_ul = array();
$classes_ul[] = ( $this->recursion_depth > 1 ? 'sub-menu' : '' );
$classes_ul[] = 'depth-' . $this->recursion_depth;
// process list wrappers
$this->output.= '<ul class="' . $this->process_classes($classes_ul) . '">';
// loop through menu items
foreach( $menu as $menu_key => $menu_val )
{
// process list items
$this->output.= '<li>';
$this->output.= $menu_val->title;
// if necessary, handle children and recurse
if( !empty($menu_val->children) )
{
// recurse, and call this again
$this->recurse_menu($menu_val->children);
}
// process list items
$this->output.= '</li>';
}
// process list wrappers
$this->output.= '</ul>';
}
}
private function process_classes($classes = array())
{
if( !$classes )
{
return;
}
return trim(implode(' ', $classes));
}
private function build_tree( $elements = array(), $parent_id = 0 )
{
$branch = array();
foreach($elements as $element)
{
if ($element->menu_item_parent == $parent_id)
{
$children = $this->build_tree($elements, $element->ID);
if ($children)
{
$element->children = $children;
}
$branch[] = $element;
}
}
return $branch;
}
}
$mynav = new Nav_Menu('Test Menu'); exit;
Resulting output
<ul class="depth-1">
<li>
One
<ul class="sub-menu depth-2">
<li>
Sub One
</li>
<li>
Sub Two
</li>
<li>
Sub Three
</li>
</ul>
</li>
<li>
Two
</li>
<li>
Three
<ul class="sub-menu depth-3">
<li>
Sub One
</li>
<li>
Sub Two
</li>
</ul>
</li>
<li>
Four
</li>
</ul>
I just created a private variable for the class, and each time I need to reference it as a storage location, I just append to it. Same as before, but no more having to pass $output down some crazy method chain.
If anyone has any other ideas that could help the community, please share!
Update your build_output method as below:
private function build_output( $menu = array() )
{
$output = '<ul>';
$output = $this->recurse_menu($menu, $output);
$output.= '</ul>';
return $output;
}
Update your recurse_menu method as below:
private function recurse_menu( $menu = array(), $output = '')
{
global $post;
if( !empty($menu))
{
$this->recursion_depth++;
// ul classes
$classes_ul = array();
$classes_ul[] = ( $this->recursion_depth > 1 ? 'sub-menu' : '' );
$classes_ul[] = 'depth-' . $this->recursion_depth;
// loop through menu items
foreach( $menu as $menu_key => $menu_val )
{
// if necessary, handle children and recurse
if( !empty($menu_val->children) )
{
// recurse, and call this again
$output.= '<li>'.$menu_val->title.'<ul class="' . $this->process_classes($classes_ul) . '">'.$this->recurse_menu($menu_val->children).'</ul></li>';
}
else {
$output.= '<li>'.$menu_val->title.'</li>';
}
}
}
return $output;
}
NOTE: I have tested it with more further sub-level and it is working fine.
I am using recursion function to display categories and subcategories in PHP.
In MySQL database table, there are 3 columns:
cat_id, parent_id, cat_name
However, I should keep tracking of unordered lists in order to add class to make a drop down menu using jQuery. So, the HTML code should look like this:
<ul>
<li class = "dropdown">Parent 1
<ul class = "sub-menu">
<li>Sub 1</li>
<li class = "dropdown">
Sub 2
<ul class = "sub-menu">
<li>Sub 2.1</li>
<li>Sub 2.2</li>
<li>Sub 2.3</li>
</ul>
</li>
<li>Sub 3</li>
<li>Sub 4</li>
<li>Sub 5</li>
</ul>
</li>
<li>Parent 2</li>
<li>Parent 3</li>
<li>Parent 4</li>
</ul>
The recursion function that I am using:
$query = "select * from categories";$result = $conn->query($query); $cats = array();while($cat = $result->fetch_assoc()){$cats_ID[$cat['cat_id']][] = $cat;$cats[$cat['parent_id']][$cat['cat_id']] = $cat;}
function build_tree($cats,$cat_id,$only_parent = false){
if(is_array($cats) and isset($cats[$cat_id])){
$tree = "<ul>\n";
if($only_parent==false){
foreach($cats[$cat_id] as $cat){
$tree .= '<li><a href = "#">'.$cat['cat_name'];
$tree .= build_tree($cats,$cat['cat_id']);
$tree .= "</a></li>\n";
}
}
$tree .= "</ul>";
}
else return null;
return $tree;
}
How can i keep track of ul lists?
This should do it like I suggested in the comment above by adding a $depth parameter.
$query = "select * from categories";
$result = $conn->query($query);
$cats = array();
while($cat = $result->fetch_assoc()){
$cats_ID[$cat['cat_id']][] = $cat;
$cats[$cat['parent_id']][$cat['cat_id']] = $cat;
}
//added a depth argument that defaults to 1
function build_tree($cats,$cat_id,$only_parent = false, $depth=1){
if(is_array($cats) and isset($cats[$cat_id])){
//check if depth is > 1, output class, else, no class.
if($depth > 1){
$tree = "<ul class='sub-menu'>\n";
} else {
$tree = "<ul>\n";
}
if($only_parent==false){
foreach($cats[$cat_id] as $cat){
//get the recursion first
//adjusted recursive call to include depth+1.
$tmp = build_tree($cats,$cat['cat_id'],$only_parent,$depth+1);
//if we are at the first depth and there is child content
if($depth == 1 && is_null($tmp)){
//add the dropdown class
$tree .= '<li class="dropdown"><a href = "#">'.$cat['cat_name'];
} else {
//no dropdown class
$tree .= '<li><a href = "#">'.$cat['cat_name'];
}
//now append the recursion
$tree .= $tmp;
$tree .= "</a></li>\n";
}
}
$tree .= "</ul>";
}
else return null;
return $tree;
}
You should be able to just replace your function with this new one and everything should just work since the newly added argument has a default value.
I was looking at the code and just don't get how to add a custom CSS class to ul tag?
<?php if (!empty($this->row->amenities)): ?>
<h3><?php echo JText::_('COM_JEA_AMENITIES')?> :</h3>
<?php echo JHtml::_('amenities.bindList', $this->row->amenities, 'ul') ?>
<?php endif ?>
Which generates:
<h3>Amenities</h3>
<ul>
<li></li>
<li></li>
<li></li>
etc.
</ul>
I need:
<h3>Amenities</h3>
<ul class="check">
<li></li>
<li></li>
<li></li>
etc.
</ul>
I'm sure, it should be something simple.
The Source of the function is -
static public function bindList($value=0, $format='raw')
{
if (is_string($value) && !empty($value)) {
$ids = explode('-' , $value);
} elseif (empty($value)) {
$ids = array();
} else {
JArrayHelper::toInteger($value);
$ids = $value;
}
$html = '';
$amenities = self::getAmenities();
$items = array();
foreach ($amenities as $row) {
if (in_array($row->id, $ids)) {
if ($format == 'ul'){
$items[] = "<li>{$row->value}</li>\n";
} else {
$items[] = $row->value;
}
}
}
if ($format == 'ul'){
$html = "<ul>\n" . implode("\n", $items) . "</ul>\n";
} else {
$html = implode(', ', $items);
}
return $html;
}
As per the source of the function the way I can see is to replace '<ul>' to '<ul class="check">'
<?php
$html = JHtml::_('amenities.bindList', $this->row->amenities, 'ul');
echo str_replace('<ul>','<ul class="check">',$html);
?>
Or you can call getAmenities function and loop through result and create your own ul and li.
Hope this helps.
I'm working on a simple CMS for a pet project. I currently have a JSON string that contains a list of page ID's and Parent page ID's for a menu structure.
I want to now convert this string into a nested or hierarchical list (ordered list).
I've tried looking looping through but seem to have ended up with an overly complex range of sub classes. I'm struggling to find a suitable light-weight solution in PHP.
Here's the JSON:
**[{"id":3,"children":[{"id":4,"children":[{"id":5}]}]},{"id":6},{"id":2},{"id":4}]**
Here's the desired output:
<ol>
<li>3
<ol>
<li>4</li>
<ol>
<li>5</li>
</ol>
</ol>
</li>
<li>6</li>
<li>2</li>
<li>4</li>
</ol>
Is there anything built in to PHP that can simplify this process? Has anyone had any experience of this before?
I'm a newbie to PHP and SE. Looking forward to hearing from you.
Here's my current progress (it's not working too well)
<ol>
<?php
$json = '[{"id":3,"children":[{"id":4,"children":[{"id":5}]}]},{"id":6},{"id":2},{"id":4}]';
$decoded = json_decode($json);
$pages = $decoded;
foreach($pages as $page){
$subpages = $decoded->children;
echo "<li>".$page->id."</li>";
foreach($subpages as $subpage){
echo "<li>".$subpage->id."</li>";
}
}
?>
</ol>
You can use recursion to get deep inside the data. If the current value is an array then recursion again. Consider this example:
$json_string = '[{"id":3,"children":[{"id":4,"children":[{"id":5}]}]},{"id":6},{"id":2},{"id":4}]';
$array = json_decode($json_string, true);
function build_list($array) {
$list = '<ol>';
foreach($array as $key => $value) {
foreach($value as $key => $index) {
if(is_array($index)) {
$list .= build_list($index);
} else {
$list .= "<li>$index</li>";
}
}
}
$list .= '</ol>';
return $list;
}
echo build_list($array);
Using a function that can recursively go through your JSON, you can get the functionality you wish. Consider the following code: (this only accounts for an attribute of id as getting listed, as your desired code shows)
$json = '[{"id":3,"children":[{"id":4,"children":[{"id":5}]}]},{"id":6},{"id":2},{"id":4}]';
function createOLList($group) {
$output = (is_array($group)) ? "<ol>" : "";
foreach($group as $attr => $item) {
if(is_array($item) || is_object($item)) {
$output .= createOLList($item);
} else {
if($attr == "id") {
$output .= "<li>$item</li>";
}
}
}
$output .= (is_array($group)) ? "</ol>" : "";
return $output;
}
print(createOLList(json_decode($json)));
This will produce the following HTML output.
<ol>
<li>3</li>
<ol>
<li>4</li>
<ol>
<li>5</li>
</ol>
</ol>
<li>6</li>
<li>2</li>
<li>4</li>
</ol>
What you're looking for is called recursion, which can be done by a function calling itself.
If you solved once to list all nodes of the list in one function, you can then apply the same function for all child-lists. As then those child-lists will do the same on their children, too.
call_user_func(function ($array, $id = 'id', $list = 'children') {
$ul = function ($array) use (&$ul, $id, $list) {
echo '<ul>', !array_map(function ($child) use ($ul, $id, $list) {
echo '<li>', $child[$id], isset($child[$list]) && $ul($child[$list])
, '</li>';
}, $array), '</ul>';
};
$ul($array);
}, json_decode('[{"id":3,"children":[{"id":4,"children":[{"id":5}]}]},{"id":6},
{"id":2},{"id":4}]', TRUE));
As this example shows, the $ul function is called recursively over the list and all children. There are other solutions, but most often recursion is a simple method here to get the job done once you've wrapped your head around it.
Demo: https://eval.in/153471 ; Output (beautified):
<ul>
<li>3
<ul>
<li>4
<ul>
<li>5</li>
</ul>
</li>
</ul>
</li>
<li>6</li>
<li>2</li>
<li>4</li>
</ul>
<?php
$json_array = array();
array_push($json_array, array(
'id' => 3,
'children' => array(
'id' => 4,
'children' => array(
'id' => 5,
)
)
));
array_push($json_array, array('id' => 6));
array_push($json_array, array('id' => 2));
array_push($json_array, array('id' => 4));
//your json object
$json_object = json_encode($json_array);
//echo $json_object;
//here is where you decode your json object
$json_object_decoded = json_decode($json_object,true);
//for debug to see how your decoded json object looks as an array
/*
echo "<pre>";
print_r($json_object_decoded);
echo "</pre>";
*/
echo "<ol>";
foreach($json_object_decoded as $node){
if(isset($node['id'])){
echo "<li>" . $node['id'];
if(isset($node['children'])){
echo "<ol>";
echo "<li>" . $node['children']['id'] . "</li>";
if(isset($node['children'])){
echo "<ol>";
echo "<li>" . $node['children']['children']['id'] . "</li>";
echo "</ol>";
}
echo "</ol>";
}
echo "</li>";
}
}
echo "</ol>";
?>
I have found that i have to fix or simplify almost every of the functions above.
So here i came with something simple and working, still recursion.
function build_list($array) {
$list = '<ul>';
foreach($array as $key => $value) {
if (is_array($value)) {
$list .= "<strong>$key</strong>: " . build_list($value);
} else {
$list .= "<li><strong>$key</strong>: $value</li>";
}
}
$list .= '</ul>';
return $list;
}
build_list(json_encode($json_string),true);
I've got a result set from adLDAP of the form
OU=LEAF1,OU=PARENT1,OU=ROOT,DC=datacenter,DC=local
OU=PARENT1,OU=ROOT,DC=datacenter,DC=local
OU=ROOT,DC=datacenter,DC=local
OU=LEAF2,OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local
OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local
OU=PARENT2,OU=ROOT,DC=datacenter,DC=local
Where each line is a string element in an array.
The tree structure it represents is :
Root
|--Parent1
|--Leaf1
|--Parent2
|--Child
|--Leaf2
and I want to generate this
<ul>
<li>root
<ul>
<li>Parent1
<ul>
<li>leaf1</li>
</ul>
</li>
<li>Parent2
<ul>
<li>Child
<ul>
<li>Leaf2</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
</ul>
I know I need to process the strings backwards, and I know the solution is recursive, but it's friday afternoon, it's a long time since I've done it and my brain is stuck.
Here is my attempt:
<?php
$lines = array(
'OU=LEAF1,OU=PARENT1,OU=ROOT,DC=datacenter,DC=local',
'OU=PARENT1,OU=ROOT,DC=datacenter,DC=local',
'OU=ROOT,DC=datacenter,DC=local',
'OU=LEAF2,OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local',
'OU=CHILD,OU=PARENT2,OU=ROOT,DC=datacenter,DC=local',
'OU=PARENT2,OU=ROOT,DC=datacenter,DC=local',
);
//build tree structure
$tree = array();
foreach ($lines as $line) {
$ancestry = getLineAncestry($line);
$node = & $tree;
foreach ($ancestry as $nodeName) {
if (! isset($node[$nodeName])) {
$node[$nodeName] = array();
}
$node = & $node[$nodeName];
}
}
print makeUl($tree);
//recurse through tree to build unordered-list
function makeUl($array) {
$result = '<ul>';
foreach ($array as $nodeName => $children) {
$result .= '<li>' . ucfirst($nodeName);
if (count($children)) {
$result .= makeUl($children);
}
$result .= '</li>';
}
$result .= '</ul>';
return $result;
}
function getLineAncestry($line) {
$result = array();
$params = explode(',', $line);
foreach ($params as $param) {
$tmp = explode('=', $param);
if ($tmp[0] == 'OU') {
$result[] = $tmp[1];
}
}
$result = array_reverse($result);
return $result;
}