Forum Moderators: coopster

Message Too Old, No Replies

Creating a dynamic menu with PHP

nearly there, just a few issues to iron out

         

HelenDev

3:52 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



As anyone who hangs around here will vouch for, I've been busy working on a little php project, and asking lots of questions here!

I shall now unveil my magnificent octopus ;) and invite all you clever peeps to help me resolve my remaining issues...

The Code..


//get the url, and trim off the first slash in order to avoid extra value in array later on
$url = substr($_SERVER['PHP_SELF'],1);

//use explode to find out what section we're in
$urlarray = explode("/", $url);
//how many levels deep are we?
$levelsdeep=(count($urlarray));

//current directory
$dirpath = "./";

//selecteddirectory
$seldirpath = $dirpath;

$countlevelsdeep = $levelsdeep;

//do this for current directory
while($countlevelsdeep>0){
$dh = opendir($seldirpath);

//loop through the contents
while (false!== ($file = readdir($dh))) {
//list only subdirectories
if (is_dir("$seldirpath/$file")) {
//put them in an array
$dirarray[] = $file;
}
}
closedir($dh);

//remove any items we don't want from our array
$deletekey = array_search('css', $dirarray);
unset($dirarray[$deletekey]);
$deletekey = array_search('Templates', $dirarray);
unset($dirarray[$deletekey]);
$deletekey = array_search('includes', $dirarray);
unset($dirarray[$deletekey]);
$deletekey = array_search('.', $dirarray);
unset($dirarray[$deletekey]);
$deletekey = array_search('..', $dirarray);
unset($dirarray[$deletekey]);

//save as new array each time
$menuarray[] = $dirarray;
unset($dirarray);

//-increment level
$countlevelsdeep--;
//add level to dir path
$seldirpath.="../";

}

echo("<pre>");
print_r($menuarray);
echo("</pre>");

$menuint = $levelsdeep;
//loop through the levels
while($menuint>=0){
echo('<ul id="'.$menuint.'">');
//get rid of any empty arrays
if($menuarray[$menuint]!=""){

foreach ($menuarray[$menuint] as $key){
echo('<li><a href="'.$key.'">'.$key.'</a></li>');
}

}
echo("</ul>");
$menuint--;
}

The Problems...

Firstly, I have an extra empty array - why is this?
Secondly, I need to make my menu have proper links which actually work!
Thirdly if anyone can see anything stoopid or long winded I'm doing, feel free to point it out!

Cheers :)

[edited by: HelenDev at 4:13 pm (utc) on June 6, 2007]

jatar_k

4:53 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Administrator 10+ Year Member



so I uploaded a copy to see what it did

it outputs 2 lists

one at the level I am at, and 1 at the level above, I will move it down to retest at a deeper level

you should probably add a trailing slash to your links

I don't get an empty array so something is not behaving as expected in all scenarios

jatar_k

4:56 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Administrator 10+ Year Member



so it does the level you are at and then walks up a level, then another, until it finds the root

these arrays are done in ascending (up the tree) order and the menu is done in descending (down the tree) order

interesting, pretty quick too, testing it on a medium size site, not bad

<added>did you want to do them in more of a tree structure and tie the levels to their upper level items?

whoisgregg

5:24 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



That's a nifty script. I knew you had to be up to something, HelenDev. :)

<added>did you want to do them in more of a tree structure and tie the levels to their upper level items?

I can't speak for Helen, but I think making that adjustment to the array structure would be the best way to make the outputting of correct links easiest.

jatar_k

5:29 pm on Jun 6, 2007 (gmt 0)

WebmasterWorld Administrator 10+ Year Member



that's what I was thinking, redo the reading to allow for easier output but I wasn't sure of the goal so didn't want to mess around too much

I have a similar script that does a sitemap and portions of it would work for the this

<added>I also thought you could add some sorting if you wanted the links alphabetical, though you may not want that.

The other issue is you might want to set an exclusion array so that directories that you don't want listed would be skipped

whoisgregg

2:01 pm on Jun 7, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



So, without completely rethinking the script and maintaining a 2 dimensional array of results (as opposed to nesting the subdirectories inside of each other), here's the code that addresses HelenDev's issues.

I also did not get empty arrays, except when they *should* be empty due to a lack of subdirectories at a particular level. You'll see the links are now correct because of the whole

$current_working_directory
nonsense at the beginning of the
while($countlevelsdeep>0)
loop.

As far as long winded, I took out an unneeded loop by testing for excluded directories before putting the directory into the

$dirarray
in the first place. I also used the
in_array()
approach to that task as discussed here [webmasterworld.com] to make the code shorter.

Overall, I would prefer to rewrite it to build a multidimensional array but it's still too early in the morning for me to wrap my ahead around that. ;)

<?php
//get the url, and trim off the first slash in order to avoid extra value in array later on
$url = substr($_SERVER['PHP_SELF'],1);
echo '<br />$url: '.$url;
//use explode to find out what section we're in
$urlarray = explode("/", $url);
$urlarray_rev = array_reverse($urlarray);
//how many levels deep are we?
$levelsdeep=(count($urlarray));
echo '<pre>'; print_r($urlarray_rev); echo '</pre>';
//current directory
$dirpath = "./";
//selecteddirectory
$seldirpath = $dirpath;
$countlevelsdeep = $levelsdeep;
//do this for current directory
while($countlevelsdeep>0){
$upDir = ($levelsdeep-$countlevelsdeep+1);
$current_working_directory = $urlarray_rev[$upDir].'/';
while( $upDir++ < $levelsdeep ){
$current_working_directory = $urlarray_rev[($upDir)].'/'.$current_working_directory;
}
$dh = opendir($seldirpath);
//loop through the contents
$hidden_directories = array( 'css', 'Templates', 'includes' );
while (false!== ($file = readdir($dh))) {
//list only subdirectories
if (is_dir("$seldirpath/$file")) {
//put them in an array
//echo '<br />'.$file;
if( $file{0}!= '.' &&!in_array($file, $hidden_directories))
$dirarray[$current_working_directory][] = $file;
}
}
closedir($dh);
//-increment level
$countlevelsdeep--;
//add level to dir path
$seldirpath.="../";
}
ksort($dirarray);
echo("<pre>");
print_r($dirarray);
echo("</pre>");
foreach($dirarray as $dir=>$dirs){
//get rid of any empty arrays
if(is_array($dirs) && count($dirs) >= 1){
echo('<ul id="'.str_replace('/','.',$dir).'">');
foreach ($dirs as $d){
echo '<li><a href="'.$dir.''.$d.'/">'.$dir.''.$d.'/</a></li>';
}
echo("</ul>");
}
$menuint--;
}
?>

HelenDev

3:07 pm on Jun 7, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Thanks whoisgregg, that works a treat :)

I also did not get empty arrays, except when they *should* be empty due to a lack of subdirectories at a particular level

I think that's what it was, and I was tired and confused at that point!

I took out an unneeded loop by testing for excluded directories

Thanks, I was thinking I probably needed to do something like that.

I also thought you could add some sorting if you wanted the links alphabetical, though you may not want that

That would probably be useful. How are they ordered by default?

The only other thing I would want to add to this, is to tidy up my menu items. I have taken out the $dir and / from the <li> and will look to strip out any underscores and replace with spaces. I would also like to have the section names in title case (capitalized), with any mini-words (and, it etc.) excluded from this.

I'll go away and work on this...

Thanks for all your help so far, I'm pleased with how it's turning out :)

HelenDev

2:28 pm on Jun 12, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



OK, coming back to this afresh after a long weekend...

I'm now back in the real world, trying to style my menu for actual use. I made a few small amends to the code, but am wondering if it needs any fundamental changes, such as the 2 dimensional array which you were all talking about.

Basically I would like to have IDs and classes so that I can make the menu item for this-page, this-pages-parent and this-pages-children all look as they should.

Also, I think it is common in most menus for the child items to appear directly underneath the parent page - at the moment they are appearing at the bottom, which could be a bit odd.

Does anyone have any bright ideas as to the simplist way to achieve this?

HelenDev

12:02 pm on Jun 14, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



OK, after a few days of head scratching, I have come up with something which I think is much improved and easier to style...


//domain
$domain = "http://www.mydomain.co.uk";


//get the url, and trim off the first slash in order to avoid extra value in array later on
$url = substr($_SERVER['PHP_SELF'],1);


//use explode to find out what section we're in
$urlarray = explode("/", $url);


//how many levels deep are we?
$levelsdeep=(count($urlarray));


if ((count($urlarray))>3){
//need to remove some levels from this until files in correct place
$countlevelsdeep = $levelsdeep-3;
while($countlevelsdeep>0){
$prefixpath.="../";
$countlevelsdeep--;
}
}


//create a reliable path to be used for include files and such
$incpath="includes/";
$filepath = $prefixpath.$incpath;
$imgpath="images/";
$imgfilepath = $prefixpath.$imgpath;


//titlecase function
function titleCase($mystring){


//turn string into array
$mystringarray = explode("_", $mystring);


// exclude mini words
$miniwords = array('and', 'for', 'of');
foreach($mystringarray as $i => $word){
if(!in_array($word, $miniwords)){
$mystringarray[$i] = ucwords($word);
}
}


//turn array back into string but with spaces between each value
$mystring = implode(" ", $mystringarray);


return $mystring;
}


//remove index.php (last key) from array
$lastkey = end(array_keys($urlarray));
unset($urlarray[$lastkey]);


//put the breadcrumb values and links in an array for later use with menu
$breadcrumbarray = array();


foreach($urlarray as $value){
$breadpath.='/'.$value;
$breadcrumbarray[$domain.$breadpath]=$value;
}
//trim root off breadcrumbarray
$root = array_shift($breadcrumbarray);


$breadcrumb = '<a href="http://www.mydomain.co.uk">My Site</a> &gt; <a href="http://www.mydomain.co.uk/intranet/">Internal</a>';
foreach($breadcrumbarray as $key => $value){
$breadcrumb.=' &gt; <a href="'.$key.'">'.titleCase($value).'</a>';
}


$linkarray = $breadcrumbarray;


//end($linkarray) = current location
$current_location = end($linkarray);


//don't show folders with these names
$hidden_directories = array( '.', '..', 'css', 'Templates', 'includes', 'images', 'js', 'icaew', 'documents');


echo('<ul>');


//top level menu
//root directory - need to calcualte this each time
$rootdir = $prefixpath."../";
// open the top level directory
if ($dh = opendir($rootdir)) {
while (($file = readdir($dh))!== false) {
if (is_dir("$rootdir/$file") &&!in_array($file, $hidden_directories)) {


//check if this is current section
if($file == $urlarray[1]){


//check if this is also the current location, and if it is, don't show the link
if($urlarray[1]!= $current_location){
echo ('<li id="current_section"><a href="../'.$prefixpath.$file.'">'.$file.'</a></li>');
}


//list parent, grandparent etc.
$menulinkarray = $linkarray;
//unset first and last values
$remove_section = array_shift($menulinkarray);
$lastmenukey = end(array_keys($menulinkarray));
unset($menulinkarray[$lastmenukey]);


foreach($menulinkarray as $key => $value){
echo('<li class="ancestors"><a href="'.$key.'">'.titleCase($value).'</a></li>');
}


//current item
echo('<li id="current_location">'.titleCase($current_location).'...</li>');


//if current item has any child directories, list them
$currentdir = "./";


if (is_dir($currentdir)) {
if ($currentdh = opendir($currentdir)) {
while (($file = readdir($currentdh))!== false) {


if (is_dir("$currentdir/$file") &&!in_array($file, $hidden_directories)) {
echo ('<li class="child_items"><a href="'.$file.'">'.titleCase($file).'</a></li>');
}
}
closedir($currentdh);
}
}


} else {
echo ('<li class="top_level"><a href="../'.$prefixpath.$file.'">'.$file.'</a></li>');
}


}
}
}
closedir($dh);


echo('</ul>');

And then of course we can add some styles (of course you can make them prettier than these!)...


#menu{width:25%; float:left;}
.top_level{margin-left:-10px;}
#current_section{font-weight:bold; background-color:#FFFF00;}
#current_location{font-weight:bold;}
.child_items{margin-left:20px; background-color:#00FFFF;}
.ancestors{background-color:#99FF99}
#menu a{text-decoration:none;}
#menu a:hover{text-decoration:underline;}

Please feel free to try it on your sites and let me know what you think or if you spot any issues.

Thanks :)

Habtom

12:15 pm on Jun 14, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I run your code and checked the output, and I kept on wondering if the code could have been half the size for the same output. :) May be.

Hab

[edited by: Habtom at 12:15 pm (utc) on June 14, 2007]

tomda

12:27 pm on Jun 14, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



You have

$domain = "http://www.mydomain.co.uk";

so breadcrumb should be

$breadcrumb = '<a href="'.$domain.'">My Site</a> &gt; <a href="'.$domain.'/intranet/">Internal</a>';

Tomda

HelenDev

12:56 pm on Jun 14, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I kept on wondering if the code could have been half the size for the same output

Feel free to show me how :)

Thanks Tomda re the tip on the breadcrumb :)

jatar_k

12:58 pm on Jun 14, 2007 (gmt 0)

WebmasterWorld Administrator 10+ Year Member



and Helen throws down the gloves ;)

Habtom

1:00 pm on Jun 14, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



>> and Helen throws down the gloves ;)

:)

HelenDev

2:35 pm on Jun 15, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I also thought you could add some sorting if you wanted the links alphabetical

That would be good - how can I incorporate that into my script?

jatar_k

2:38 pm on Jun 15, 2007 (gmt 0)

WebmasterWorld Administrator 10+ Year Member



you would have to sort them once read into an array

HelenDev

11:04 am on Jun 19, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Right, in response to Habtom's point about the code being lengthy, and also to sort the problem with the ordering, I *think* I need to create a reusable function to read the contents of a directory into an array and sort it alphabetically.

This is how far I've got, but being a bit new to functions, I don't think it's quite right and it's not working...


//don't show folders with these names
$hidden_directories = array( '.', '..', 'css', 'Templates', 'includes', 'images', 'js', 'icaew', 'documents');


//create function to read files in directory, put them in an array and order it alphabetically
function readDirList($thisdir,$thisdirname){


if (is_dir($thisdir)) {
if ($thisdh = opendir($thisdir)) {


//create array
$thisarray = array();


while (($file = readdir($thisdh))!== false) {


if (is_dir("$thisdir/$file") &&!in_array($file, $hidden_directories)) {
$thisarray[] = $file;
}


}


}
closedir($thisdh);
}
sort($thisarray); //puts an array in alphabetical order


}


readDirList("./","current_directory");

It needs to be reusable for the current directory, the parent directory, and any child directory, and it needs to create a different array for each, as before.

HelenDev

1:59 pm on Jun 19, 2007 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



OK, I think I figured it out myself...


//create function to read files in directory, put them in an array and order it alphabetically
function readDirList($thisdir){

//don't show folders with these names
$f_hidden_directories = array( '.', '..', 'css', 'Templates', 'includes', 'images', 'js', 'icaew', 'documents');

if (is_dir($thisdir)) {
if ($thisdh = opendir($thisdir)) {

$thisarray = array();

while (($file = readdir($thisdh))!== false) {
if (is_dir("$thisdir/$file") &&!in_array($file, $f_hidden_directories)) {
$thisarray[] = $file;
}
}

}
closedir($thisdh);
}
sort($thisarray); //puts an array in alphabetical order
return $thisarray;
}