I am trying to create a tree from an array like this:
array('admin/home', 'admin/home/page', 'admin/test', 'team/tree/template', 'site');
Or more visually seen as:
admin/home
admin/home/page
admin/test
team/tree/template
site
However, I also need the last nested string of each array to become bolded to signify that it is a webpage itself.
The final product should look like:
admin
home
page
test
team
tree
template
site
I have used a few different solutions from this site but I have not been able to get the right outcome.
Any ideas or examples on where I should start would be greatly appreciated, thanks.
You are probably looking for something like this:
<?php
$items = array('admin/home', 'admin/home/page', 'admin/test', 'site', 'team/tree/template');
sort($items);
$previous = array();
foreach ($items as $item) {
$path = explode('/', $item);
// $i is common nesting level of the previous item
// e.g. 0 is nothing in common, 1 is one level in common, etc.
for ($i = 0; $i < min(count($path), count($previous)) && ($path[$i] == $previous[$i]); $i++) { }
// close <ul> from previous nesting levels:
// that is, we close the amount of levels that the previous and this one have NOT in common
for ($k = 0; $k < count($previous)-$i-1; $k++) { echo "</ul>"; }
// $j is the current nesting level
// we start at the common nesting level
for ($j = $i; $j < count($path); $j++) {
// open <ul> for new nesting levels:
// i.e., we open a level for every level deeper than the previous level
if ($j >= count($previous))
echo "<ul>";
echo "<li>";
// make the path bold if the end of the current path is reached
if (count($path)-1 == $j)
echo "<b>" . $path[$j] . "</b>";
else
echo $path[$j];
echo "</li>";
}
$previous = $path;
}
// close remaining <ul>
for ($k = 0; $k < count($previous); $k++) { echo "</ul>"; }
?>
Note that I order the array to make sure that all nested items come next to each other.
It might have some bugs, but I guess you can work them out yourself. Note that I close the list items before opening a nested <ul>. This is not really how it should; I guess you actually should open the nested <ul> in the final <li> at that level, but you can figure that out yourself.
Update I updated the code to properly close the <ul>'s in some cases.
Related
I know how to count elements inside a loop, but tried and can´t find a solution. Actually, I can count the elements in loop in this way:
$i = 0;
foreach ($Contents as $item)
{
$i++;
}
echo $i;
But in my case, I have large function and I can do this, but need more time for count elements inside. I can´t put the string for showing number elements inside loop outside of the function. I must show inside loop all number of elements and show as this. Here's an example:
$i = 0;
foreach ($Contents as $item)
{
print "Number Elements it´s : $i";
print "Element ".$i."<br>";
$i++;
}
The problem here is that the phrase "number elements it´s" always repeats, because inside loop, and all time $i show me 0,1,2,3,4,5 ...... and all time the same as number of elements inside loop.
My idea and question is if it´s possible inside loop to show the number of elements and after this show the elements as this structure when executing the script:
Númber elements it´s : 12
Element 1
Element 2
Element 3
.
.
.
.......
This it´s my question. I hope you understand it all. Thanks in advance.
As mentioned in the comments, youu will need to use the count() function for that. If you insist on having it inside the loop, simply do it like this:
$i = 0;
foreach ($Contents as $item)
{
if($i == 0) {
print "Number of Elements is : " . count($Contents) . "<br><br>";
}
print "Element ".$i."<br>";
$i++;
}
I have a php while loop, but I want to insert a "static" code block into it after the third item in the loop and then continue the loop after the static content. Can someone help me update my code to accomplish the "desired outcome" below?
My Code
$x = 0;
while($x <= 5) {
$contentItems = $relatedArray[$rand_keys[$x]];
$contentItems = explode('|', $contentItems);
echo '
<p><img class="faux" src="'.$contentItems[4].'"></p>
';
$x++;
}
Desired Outcome
Loop Content
Loop Content
Loop Content
**STATIC CONTENT**
Loop Content
Loop Content
Loop Content
Just add an if conditional inside of your while loop that checks against the index:
$x = 0;
while($x <= 5) {
// Will only trigger halfway through the loop
if ($x == 3) {
echo '**STATIC CONTENT**';
}
$contentItems = $relatedArray[$rand_keys[$x]];
$contentItems = explode('|', $contentItems);
echo '<p><img class="faux" src="'.$contentItems[4].'"></p>';
$x++;
}
Note that this will still output the original item at this position. If you want to replace it instead, you'll want to wrap everything related to $contentItems inside of an else conditional :)
$array=array('menu1','menu2','menu3','menu4','menu5','menu6','menu7','menu8','menu9','menu10');
I storied some menu names into an array, Then output some like
in diffirent page, the menu always show 5 items and current page is always 2nd.
And if the current page is in this case, it will loop display menu1, menu2, menu3 after menu10.
foreach($array as $k=>$r){
if($r==$current){
$n=$k;//count current memu position
}
}
echo '<ul>';
foreach($d as $k=>$r){
if($n>1&&$n<7){//normal situation like first image
if($k==($n-1)){
echo '<li><a>'.$r.'</a></li>';
}else if($k==$n){
echo '<li class="current">'.$r.'</li>';
}else{
echo '<li><a>'.$r.'</a></li>';
}
}else{
//How to do in the case of the second image?
}
}
echo '</ul>';
I think you would also like the menu look like this when the current selection is menu1:
menu10
menu1 [selected]
menu2
menu3
menu4
Here is the algorithm. It looks confusing but I am sure you can figure it out.
$array=array('menu1','menu2','menu3','menu4','menu5','menu6',
'menu7','menu8','menu9','menu10');
$current = 'menu1';
$startIndex = array_search($current, $array)-1;
$total = count($array);
$result = array();
for($index = $startIndex ; $index <$startIndex+5; $index++){
$result[] = $array[($index+$total)%$total]; //using % operator
}
print_r($result);
Try this:
if($k==($n-1)){
echo '<li><a>'.$r.'</a></li>';
}else if($k==$n){
echo '<li class="current">'.$r.'</li>';
}else{
if($k >= count($d)-1)
echo ''<li><a>'.$d[$k-count($d)+1].'</a></li>';
else
echo ''<li><a>'.$r.'</a></li>';
}
This actually should work for both cases, so you can remove the big if statement and put this as the body of your foreach.
How do I put the first foreach statement's output in one column in a table and the other foreach statement's output in another column. I tried something but it put it all in one column for some reason. Here is my code:
<table border="0" align="center">
<?php
foreach($anchors as $a) {
$text = $a->nodeValue;
$href = $a->getAttribute('href');
$i++;
if ($i > 16) {
if (strpos($text, "by owner") === false) {
if (strpos($text, "map") === false) {
echo "<tr><td><a href =' ".$href." '>".$text."</a><br/></td></tr>";
}
}
}
foreach($span as $s) {
echo "<tr><td>".$s->nodeValue."</td></tr>";
}
}
?>
</table>
<tr></tr> marks a row. <td></td> marks a column. To make 2 columns, use just one set of <tr> tags per iteration, with two sets of <td></td>s between them.
That said, what exactly is $span? Does it contain the same number of elements as $anchors, and you want to display one item from each per row? If so you'll need to restructure your code a bit. There are several ways to do this—here's a simple way:
<table border="0" align="center">
<?php
$i = 0;
foreach($anchors as $a) {
echo "<tr>";
$text = $a->nodeValue;
$href = $a->getAttribute('href');
if ($i >= 16) {
if (strpos($text, "by owner") === false) {
if (strpos($text, "map") === false) {
echo "<td><a href =' ".$href." '>".$text."</a><br/></td>";
}
}
} else {
echo "<td></td>"; #output a blank cell in the first column
}
echo "<td>" . $span[$i]->nodeValue . "</td>";
echo "</tr>";
++$i
}
?>
</table>
EDIT: It looks like your $span is a DOMNodeList object, not an array. I don't have experience with this, but it looks like you can use the DOMNodelist::item function to get the current item in the list (see http://php.net/manual/en/domnodelist.item.php):
echo "<td>" . $span->item($i)->nodeValue . "</td>";
So try changing the respective line in my answer to that.
It is hard without an idea of the data, but something like this perhaps:
// start a table
echo '<table>';
// for as long as there are elements in both span and anchors
for ($i=0; $i < $anchors->length && $i < $span->length; $i++) {
// start a new table row
echo '<tr>';
// get the current span and anchor
$a = $anchors->item($i);
$s = $span->item($i);
// print them
$text = $a->nodeValue;
$href = $a->getAttribute('href');
// col 1, number
echo '<td>'.$i.'</td>';
// col 2, anchor
echo '<td>'.$text.'</td>';
// col 3, span
echo '<td>'.$s->nodeValue.'</td>';
// close the table row
echo '</tr>';
}
// close the table
echo '</table>';
(code not tested) It is difficult to be more specific without the actual data.
This uses the 'current' and 'next' built in to php.
A few hints/remarks/sidenotes that may help you on the way:
- Note that I used single quotes cause they are much better for
performance (double quotes will be interpreted by php).
- Try to use as little loops (for, while, foreach) as possible. They are a powerfull
tool, but can drain memory and performance quickly!
- Only nest loops if you are working with multiple dimensions (array inside array),
which is not the case here (I think)
- Try to limit the number of nested blocks (if inside if inside if inside loop). I try to go never deeper then 2 levels (which is not an absolute rule off course, just a good standard). If not possible create a function.
- Comment your code! I have difficulty understanding your code (and I write PHP daily for a living), and I can imagine you will to in a couple of weeks. Commenting may look like a waste of time, but it will ease debugging a lot, and is a blessing when updating your (or someone elses) code later on!
EDIT:
I just noticed you are not working with a DOMNodeList and not an array, so I updated my code. Should work fine, and a lot cleaner code imo. Like I said, hard without seeing the data...
I have a table in a database that contains a variety of paths to pages of my website. Each path is listed only one time. I currently have a very long and convoluted series of queries and PHP to pull all these and rewrite the data into an unordered list (to create a menu for my website). It seems that there is probably a relatively simple looping approach that would work MUCH more efficiently, but I cannot seem to get anything working. I have found TONS of PHP scripts that create UL lists from file trees, but all of them either don't work or can't handle the inherently not recursive nature of my query results (some require multi-dimensional arrays of my paths, which would be fine except for my trouble with creating those). I did find a script that works pretty close, but it formats the <ul> portion incorrectly by placing sub-lists outside of the <li> section (I will explain below)
Here is a sample:
DB returns the following in results array:
about/contact/
about/contact/form/
about/history/
about/staff/
about/staff/bobjones/
about/staff/sallymae/
products/
products/gifts/
products/widgets/
and I want to create the following output:
<ul>
<li>about/
<ul>
<li>about/contact/
<ul>
<li>about/contact/form/</li>
</ul>
</li>
<li>about/history/</li>
<li>about/staff/
<ul>
<li>about/staff/bobjones/</li>
<li>about/staff/sallymae/</li>
</ul>
</li>
</ul>
</li>
<li>products/
<ul>
<li>products/gifts/</li>
<li>products/widgets/</li>
</ul>
</li>
</ul>
So I got very close with a script found here: http://www.daniweb.com/forums/thread285916.html but I have run into a problem. It turns out that the script that I found creates improperly formatted UL lists. In a CORRECT situation, a sub-list is contained within the <li> of the parent element. In this scripting, the parent <li> is closed and then a <ul> block is inserted. The overall script is actually fairly elegant in the way that it keeps up with the levels and such, but I cannot wrap my head around it enough to figure out how to fix it. I have the whole thing in a function here:
function generateMainMenu()
{
global $db;
$MenuListOutput = '';
$PathsArray = array();
$sql = "SELECT PageUrlName FROM `table`";
$result = mysql_query($sql, $db) or die('MySQL error: ' . mysql_error());
while ($PageDataArray = mysql_fetch_array($result))
{
$PathsArray[] = rtrim($PageDataArray['PageUrlName'],"/"); //this function does not like paths to end in a slash, so remove trailing slash before saving to array
}
sort($PathsArray);// These need to be sorted.
$MenuListOutput .= '<ul id="nav">'."\n";//get things started off right
$directories=array ();
$topmark=0;
$submenu=0;
foreach ($PathsArray as $value) {
// break up each path into it's constituent directories
$limb=explode("/",$value);
for($i=0;$i<count($limb);$i++) {
if ($i+1==count($limb)){
// It's the 'Leaf' of the tree, so it needs a link
if ($topmark>$i){
// the previous path had more directories, therefore more Unordered Lists.
$MenuListOutput .= str_repeat("</ul>",$topmark-$i); // Close off the Unordered Lists
$MenuListOutput .= "\n";// For neatness
}
$MenuListOutput .= '<li>'.$limb[$i]."</li>\n";// Print the Leaf link
$topmark=$i;// Establish the number of directories in this path
}else{
// It's a directory
if($directories[$i]!=$limb[$i]){
// If the directory is the same as the previous path we are not interested.
if ($topmark>$i){// the previous path had more directories, therefore more Unordered Lists.
$MenuListOutput .= str_repeat("</ul>",$topmark-$i);// Close off the Unordered Lists
$MenuListOutput .= "\n";// For neatness
}
// (next line replaced to avoid duplicate listing of each parent)
//$MenuListOutput .= "<li>".$limb[$i]."</li>\n<ul>\n";
$MenuListOutput .= "<ul>\n";
$submenu++;// Increment the dropdown.
$directories[$i]=$limb[$i];// Mark it so that if the next path's directory in a similar position is the same, it won't be processed.
}
}
}
}
$MenuListOutput .= str_repeat("</ul>",$topmark+1);// Close off the Unordered Lists
return $MenuListOutput."\n\n\n";
}
and it returns something like this:
<ul id="nav">
<li>about</li>
<ul>
<li>history</li>
<li>job-opportunities</li>
<li>mission</li>
<li>privacy-policy</li>
</ul>
<li>giftcards</li>
<li>locations</li>
<ul>
<li>main-office</li>
<li>branch-office</li>
</ul>
<li>packages</li>
</ul>
Anyone have an idea of where I need to add in some additional logic and how I can accomplish this? Other ideas on a better way to do this? It seems like this is such a common issue that there would be a simple/standard method of handling something like this. Maybe if I could figure out how to create a multi-dimensional array from my paths then those could be iterated to make this work?
EDIT: More Complex :-(
I tried casablanca's response and it worked perfectly...except I then realized that now I have a follow-up to make things more difficult. In order to display the "name" of the page, I need to also have that info in the array, thus the path probably works better as the array key and the name in the value. Any thoughts on changing like this:
$paths = array(
"about/contact/ " => "Contact Us",
"about/contact/form/ " => "Contact Form",
"about/history/ " => "Our History",
"about/staff/ " => "Our Staff",
"about/staff/bobjones/ " => "Bob",
"about/staff/sallymae/ " => "Sally",
"products/ " => "All Products",
"products/gifts/ " => "Gift Ideas!",
"products/widgets/ " => "Widgets"
);
and then using something like this line within the buildUL function:
echo ''.$paths[$prefix.$key].'';
Edit:
Changed to cater for updated question.
I'm using an array index of __title to hold the page title. As long as you never have a directory in your tree called __title this should be fine. You're free to change this sentinel value to anything you wish however.
I have also changed it so the list building function returns a string, so that you can store the value for use later in your page. (You can of course just do echo build_list(build_tree($paths)) to output the list directly.
<?php
$paths = array(
'about/contact/' => 'Contact Us',
'about/contact/form/' => 'Contact Form',
'about/history/' => 'Our History',
'about/staff/' => 'Our Staff',
'about/staff/bobjones/' => 'Bob',
'about/staff/sallymae/' => 'Sally',
'products/' => 'All Products',
'products/gifts/' => 'Gift Ideas!',
'products/widgets/' => 'Widgets'
);
function build_tree($path_list) {
$path_tree = array();
foreach ($path_list as $path => $title) {
$list = explode('/', trim($path, '/'));
$last_dir = &$path_tree;
foreach ($list as $dir) {
$last_dir =& $last_dir[$dir];
}
$last_dir['__title'] = $title;
}
return $path_tree;
}
function build_list($tree, $prefix = '') {
$ul = '';
foreach ($tree as $key => $value) {
$li = '';
if (is_array($value)) {
if (array_key_exists('__title', $value)) {
$li .= "$prefix$key/ ${value['__title']}";
} else {
$li .= "$prefix$key/";
}
$li .= build_list($value, "$prefix$key/");
$ul .= strlen($li) ? "<li>$li</li>" : '';
}
}
return strlen($ul) ? "<ul>$ul</ul>" : '';
}
$tree = build_tree($paths);
$list = build_list($tree);
echo $list;
?>
Indeed a multi-dimensional would help here. You can build one by splitting each path into components and using those to index into the array. Assuming $paths is your initial array, the code below will build a multi-dimensional array $array with keys corresponding to the path components:
$array = array();
foreach ($paths as $path) {
$path = trim($path, '/');
$list = explode('/', $path);
$n = count($list);
$arrayRef = &$array; // start from the root
for ($i = 0; $i < $n; $i++) {
$key = $list[$i];
$arrayRef = &$arrayRef[$key]; // index into the next level
}
}
You can then iterate over this array using a recursive function, which you can use to naturally build a recursive UL list as in your example. In each recursive call, $array is a sub-array of the entire array this is being currently processed and $prefix is the path from the root to the current sub-array:
function buildUL($array, $prefix) {
echo "\n<ul>\n";
foreach ($array as $key => $value) {
echo "<li>";
echo "$prefix$key/";
// if the value is another array, recursively build the list
if (is_array($value))
buildUL($value, "$prefix$key/");
echo "</li>\n";
}
echo "</ul>\n";
}
The initial call would simply be buildUL($array, '').