The Horns Of A Dilemma
How many times have you sat down to code a script and - halfway through - thought to yourself, “Didn’t I do something similar just last week?”
If you’re anything like the average Web developer, you probably ask yourself this question at least once every few days. And more often than not, you’re torn between coding the same functions again (because you’re already halfway there and looking for last week’s code just isn’t worth the effort) and spending an hour searching for that itty-bitty script on your twenty-terabyte hard drive (because it’s just more convenient to modify last week’s code than to write it all over again.)
It’s to resolve precisely this sort of dilemma that a bunch of white-haired software gurus (who, according to legend, live on a snowy mountain peak in the Himalayas and spend most of their time coding algorithms to calculate the value of pi to the nth decimal) came up with the concept of object-oriented programming. Very simply, object-oriented programming allows developers to create reusable, extensible program modules in order to speed up code development and maintenance.
Now, you may not know this, but my favourite language and yours, PHP, comes with some pretty powerful OOP capabilities. And over the course of this article, I’m going to demonstrate some of them by building an object to address a very common task - generating a menu tree on a Web site. That’s not all, though - once I’ve successfully created a Menu object, I’m going to torture-test it with some of the most popular menu systems available on the Web to see if it does, in fact, offer any significant advantages.
If all goes well, this experiment should teach you a little about the theory and possible applications of OOP; provide you with a Menu class which is (hopefully) useful to you in your development activities; and perhaps even spark off some ideas for using PHP classes in your next project. If, on the other hand, I crash and burn, you’ll have something to snicker over at the pub tonight.
Sounds like fun? Keep reading.
Back To Class
Before we begin, let’s just go over the basics quickly:
In PHP, a “class” is simply a set of program statements which perform a specific task. A typical class definition contains both variables and functions, and serves as the template from which to spawn specific instances of that class.
Once a class has been defined, PHP allows you to spawn as many instances of the class as you like. These instances of a class are referred to as “objects”. Each of these instances is a completely independent object, with its own properties and methods, and can thus be manipulated independently of other objects.
This comes in handy in situations where you need to spawn more than one instance of an object - for example, two simultaneous database links for two simultaneous queries, or two shopping carts. Classes also help you to separate your code into independent modules, and thereby simplify code maintenance and changes.
A class definition typically looks like this:
<?php
class ShoppingCart
{
// this is where the properties are defined
var $items;
var $quantities;
var $prices;
...
// this is where the methods are defined
function validate_credit_card()
{
// code goes here
}
...
}
?>
Once the class has been defined, an object can be spawned with the “new” keyword and assigned to a PHP variable,
<?php
$myCart = new ShoppingCart;
?>
which can then be used to access all object methods and properties.
<?php
// accessing properties
$myCart->items = array("eye of newt", "tail of lizard", "wings of bat");
$myCart->quantities = array(9, 4, 14);
// accessing methods
$myCart->validate_credit_card();
?>
What’s On The Menu?
So that’s the theory. Let’s now spend a few minutes discussing the rationale behind the Menu object I plan to build.
Conceptually, a Web site can be considered as a combination of two things: menus and content. Menus are used to organize and classify the type of content, and to offer one or more navigational paths to specific content modules.
Now, although a menu may be visually presented in a number of different ways, there are certain common elements present in every menu:
-
Most menus are broken into levels, with each level more focused than the last; this hierarchical structure is sometimes referred to as a “menu tree”.
-
Every menu tree consists of nodes connected to each other by branches.
-
A node may have one or more children, but can have only one parent.
Using these common principles, it is possible to build a Menu object which exposes certain generic methods. These methods will have nothing to do with the visual presentation of the menu tree; rather, they provide a simple API to various menu attributes and relationships, and can be used by client- or server-side scripts which are more closely connected to the presentation layer.
Children And Their Parents
Now, the Menu object that I plan to build actually consists of two components: a database, and a series of functions to interact with it. The database contains all the raw data needed to generate the menu tree, while the class contains all the functions needed to massage the data into a useful format.
I plan to use a very simple mySQL table to store all my menu information, as well as the relationships between the various levels of the tree - take a look:
#
# Table structure for table 'menu'
#
DROP TABLE IF EXISTS menu;
CREATE TABLE menu (
id tinyint(3) unsigned NOT NULL auto_increment,
label varchar(255) NOT NULL,
link varchar(255),
parent tinyint(3) unsigned DEFAULT '0' NOT NULL,
PRIMARY KEY (id)
);
#
# id - unique identifier for each node
# label - descriptive text for each node
# link - URL for each node
# parent - id of this node's parent
#
This design makes it easy to represent a hierarchical menu tree in terms of database records. For example, I could represent the following visual tree
USA
|
| -- California
|
| -- Los Angeles
|
| -- Massachusetts
|
| -- Boston
United Kingdom
|
| -- London
as a series of records in the “menu” table.
mysql> SELECT * FROM menu;
+----+------+----------------+--------+
| id | link | label | parent |
+----+------+----------------+--------+
| 1 | | USA | 0 |
| 2 | | California | 1 |
| 3 | | Los Angeles | 2 |
| 4 | | Massachusetts | 1 |
| 5 | | Boston | 4 |
| 6 | | United Kingdom | 0 |
| 7 | | London | 6 |
+----+------+----------------+--------+
7 rows in set (0.00 sec)
I Say Method, You Say Madness…
With the database design out of the way, it’s now time to begin work on the methods which will interact with the records in the database. Before actually sitting down to code the class, it’s a good idea to spend some time listing the important methods, together with their purpose. Here’s my initial cut:
query($query) - execute an SQL query;
get_children($node) - return a collection of this node’s children;
get_parent($node) - return the identifier of this node’s parent;
get_label($node) - return the name of this node
get_type($node) - return the node type (leaf or branch)
is_root_node($node) - is this node at the root level or not?
These are the essential methods; there may be more, which I will add as development progresses.
Let’s now begin by setting up the class definition:
<?php
// menu.class.php
// methods to obtain node and tree relationships
class Menu
{
}
?>
Now, since this class will be talking to a database, I need to add a few variables to hold database-specific information.
<?php
class Menu
{
// set up some variables with default values
// to hold database parameters
// hostname, user and password
var $hostname;
var $user;
var $password;
// database and table containing menu data
var $db;
var $table;
}
?>
PHP makes it possible to automatically execute a specific function when a new instance of a class is spawned. This function is referred to as a “constructor” and must have the same name as the class.
In this case, I plan to initialize my Menu object with certain default values for the various database parameters. I have the option of assigning these variable-value pairs individually, or writing a method to assign them all in one fell swoop; I pick the latter.
<?php
class Menu
{
// set up some variables
// snip
// constructor
function Menu()
{
$this->set_database_parameters("localhost", "me", "bs49h5634", "apps", "menu");
}
// function: set database parameters
// returns: none
function set_database_parameters($hostname, $user, $password, $db, $table)
{
$this->hostname = $hostname;
$this->user = $user;
$this->password = $password;
$this->db = $db;
$this->table = $table;
}
}
?>
In case you’re wondering, the $this prefix provides a convenient way to access variables and functions which are “local” to the class
Now, all the methods listed above will be querying the database for information. Since the connect-and-query code is common to all of them, I can extract it into a separate method named query().
<?php
class Menu
{
// other methods
// function: execute query $query
// returns: result identifier
function query($query)
{
// connect
$connection = mysql_connect($this->hostname, $this->user, $this->password) or die ("Cannot connect to database");
// run query
$ret = mysql_db_query($this->db, $query, $connection) or die ("Error in query: $query");
// return result identifier
return $ret;
}
}
?>
All my object methods can now simply use query() to execute SQL queries on the database. Further - if I ever decide to move to another database, I need only update the code in this single function to ensure that my class will continue to work with the new system.
Playing With Nodes
With the underlying, database-specific layer in place, I can now begin work on the main object methods. The first of these is also one of the simplest - it accepts a node id and returns the id of the node’s parent.
<?php
class Menu
{
// other methods
// function: get parent
// returns: node id
function get_parent($id)
{
$query = "SELECT parent FROM $this->table WHERE id = '$id'";
$result = $this->query($query);
$row = mysql_fetch_row($result);
return $row[0];
}
}
?>
An offshoot of this is the is_root_node() method, used to test whether a particular node is at the base of the menu tree.
<?php
class Menu
{
// other methods
// function: is this node at the root of the tree?
// returns: boolean
function is_root_node($id)
{
if($this->get_parent($id) == 0)
{
return 1;
}
else
{
return 0;
}
}
}
?>
The get_label() and get_link() methods accept a node id and return the corresponding text and URL from the table.
<?php
class Menu
{
// other methods
// function: get label for $id
// returns: string
function get_label($id)
{
$query = "SELECT label FROM $this->table WHERE id = '$id'";
$result = $this->query($query);
$row = mysql_fetch_row($result);
return $row[0];
}
// function: get link for $id
// returns: string
function get_link($id)
{
$query = "SELECT link FROM $this->table WHERE id = '$id'";
$result = $this->query($query);
$row = mysql_fetch_row($result);
return $row[0];
}
}
?>
Using the menu table constructed a few pages ago, the code
<?php
$obj = new Menu();
echo $obj->get_label(3);
?>
would return
Los Angeles
Rounding Up The Family
Next, one of the most useful methods in this collection - the get_children() method. This method accepts a node id and returns an array containing the next level of the menu tree.
<?php
class Menu
{
// other methods
// function: get next level of menu tree
// returns: array
function get_children($id)
{
$query = "SELECT id, label, link FROM $this->table WHERE parent = '$id'";
$result = $this->query($query);
$count = 0;
while ($row = mysql_fetch_array($result))
{
$children[$count]["id"] = $row["id"];
$children[$count]["label"] = $row["label"];
$children[$count]["link"] = $row["link"];
$count++;
}
return $children;
}
}
?>
This method accepts a node id and queries the database for a list of items which reference that node in the “parent” column. These records are packaged as an array of arrays and returned to the calling function. Here’s an example of how it could be used:
<?php
$obj = new Menu();
// get children
$arr = $obj->get_children(1);
echo "<ul>";
// iterate through array
for ($x=0; $x<sizeof($arr); $x++)
{
echo "<li>" . $arr[$x]["label"];
}
echo "</ul>";
?>
And this correctly displays the children of node id 1 (USA) to be
California
Massachusetts
A useful corollary of this is the get_type() method, which can be used to identify whether a particular node on the tree has children or not - in other words, whether it is a leaf or a branch.
<?php
class Menu
{
// other methods
// function: test whether this id is a branch or leaf
// returns: boolean
function get_type($id)
{
if($this->get_children($id) )
{
return 1;
}
else
{
return 0;
}
}
}
?>
The get_ancestors() method does the reverse of the get_children() method - it returns a list of nodes between the tree root and the supplied node identifier, starting from the top of the menu tree and proceeding downwards.
<?php
class Menu
{
// other methods
// function: return a list of this node's parents
// by travelling upwards all the way to the root of the tree
// returns: array
function get_ancestors($id, $count = 0)
{
// get parent of this node
$parent = $this->get_parent($id);
// if not at the root, add to $ancestors[] array
if($parent)
{
$this->ancestors[$count]["id"] = $parent;
$this->ancestors[$count]["label"] = $this->get_label($parent);
$this->ancestors[$count]["link"] = $this->get_link($parent);
// recurse to get the parent of this parent
$this->get_ancestors($this->ancestors[$count]["id"], $count+1);
// all done? at this stage the array contains a list in bottom-up order
// reverse the array and return
return array_reverse($this->ancestors);
}
}
}
?>
Returning to the example above, an attempt to find out the ancestors of node id 5 (Boston)
<?php
$obj = new Menu();
// get children
$arr = $obj->get_ancestors(5);
echo "<ul>";
// iterate through array
for ($x=0; $x<sizeof($arr); $x++)
{
echo "<li>" . $arr[$x]["label"];
}
echo "</ul>";
?>
would return
USA
Massachusetts
Finally, the print_menu_tree() method comes in handy while debugging, if you need to visually see the complete menu tree with its internal dependencies.
<?php
class Menu
{
// other methods
// function: display complete menu tree (useful when debugging)
// returns: HTML list
function print_menu_tree($id = 0)
{
$result = $this->get_children($id);
echo "<ul>";
for ($x=0; $x<sizeof($result); $x++)
{
echo "<li>" . $result[$x]["label"] . "[" . $result[$x]["id"] . "]";
$this->print_menu_tree($result[$x]["id"]);
}
echo "</ul>";
}
}
?>
Saving My Bookmarks
At this stage, I think I have enough building blocks to actually begin using this class to build menu trees. Keep in mind, though, that I’ve been wrong before, and so my initial feeling of satisfaction may soon disappear.
The only way to find out for sure is to try building a tree to see if the methods exposed by the class are simple and generic enough to be used in a variety of situations - so let’s do that. I will attempt to use this Menu class to build a simple Web portal, which has links classified into hierarchical categories (a lot like the Open Directory Project at http://www.dmoz.org/)
This is a good time to download the accompanying source code, which contains complete versions of the SQL records displayed below, together with a copy of the final Menu class.
Since this is a portal, my database needs to reflect the various categories and links. Here’s a snippet:
mysql> SELECT id, link, label, parent FROM menu3;
+----+-------------------------------------+----------------+--------+
| id | link | label | parent |
+----+-------------------------------------+----------------+--------+
| 1 | | Server Side | 0 |
| 2 | | Client Side | 0 |
| 3 | | Tools | 0 |
| 4 | | DevTalk | 0 |
| 5 | http://www.devshed.com/ClipScripts/ | ClipScripts | 0 |
| 6 | http://www.devshed.com/rdf.html | DevShed RDF | 0 |
| 7 | http://www.devshed.com/Propaganda | Propaganda! | 0 |
| 8 | http://www.ngenuity.com/advertise/ | About DevShed | 0 |
| 9 | | Administration | 1 |
+----+-------------------------------------+----------------+--------+
My user interface should clearly reflect this menu tree, by making a distinction between “categories” and “links”. A click on a category reveals the sub-categories and links under it, while a click on a link directs the browser to the appropriate content module or URL.
Here’s the script to accomplish this:
<?php
// set default id to 0
if (!$id) { $id = 0; }
// include class
include("menu.class.php");
// create Menu
$obj = new Menu;
// get next level
$children = $obj->get_children($id);
// check to see if items are "leaves" or "branches" on the tree
for ($x=0; $x<sizeof($children); $x++)
{
if($obj->get_type($children[$x]["id"]) == 1)
{
$branches[] = $children[$x];
}
else
{
$leaves[] = $children[$x];
}
}
// get lineage from tree root (used to create navigation path)
$ancestors = $obj->get_ancestors($id)
?>
<html>
<head>
<basefont face="Arial">
</head>
<body bgcolor="White" link="Black" vlink="Black">
<img src="logo.gif" height=50 width=206 border=0 alt="">
<table width="100%" border="0" cellspacing="0" cellpadding="3" bgcolor="#9898D0">
<tr>
<td height=20>
<font color="Black"><b>
<?php
// use $ancestors[] to set up path
for ($x=0; $x<sizeof($ancestors); $x++)
{
$path .= "<a href=" . $PHP_SELF . "?id=" . $ancestors[$x]["id"] . ">" . $ancestors[$x]["label"] . "</a>" . " > ";
}
// add current level to path and print
$path .= $obj->get_label($id);
echo $path;
?>
</b></font>
</td>
<td align=right><?php if ($id > 0) { ?><font color="Black"><b><a href="<?php echo $PHP_SELF; ?>">Top</a></b></font> <?php } ?></td>
</tr>
</table>
<p>
<?php
if ($branches)
{
?>
<b><font color="#9898D0" size="+1">Categories</font></b>
<ul>
<?php
// print branches here
for ($x=0; $x<sizeof($branches); $x++)
{
echo "<li><a href=" . $PHP_SELF . "?id=" . $branches[$x]["id"] . ">" . $branches[$x]["label"] . "</a><br>";
}
?>
</ul>
<?php
}
?>
<p>
<?php
if ($leaves)
{
?>
<b><font color="#9898D0" size="+1">Links</font></b>
<ul>
<?php
// print leaves here
for ($x=0; $x<sizeof($leaves); $x++)
{
echo "<li><a href=" . $leaves[$x]["link"] . ">" . $leaves[$x]["label"] . "</a><br>";
}
?>
</ul>
<?php
}
?>
</body>
</html>
In this case, I’m first using the get_children() method to obtain a list of all items under the current tree branch. I’m then using the get_type() method to split the list of child nodes into two separate arrays, $branches and $nodes, and formatting and displaying each appropriately. Finally, with the help of the get_ancestors() method, I’m building a hierarchical, clickable trail leading to the current node - just like any good portal would.
Here’s what the end result looks like:
Reaching Higher
Now, while that’s all fine and dandy, a portal is perhaps the simplest application of this class. It remains to be seen if it can be used with other, more complex menu interfaces. So let’s put it to the test, by putting it in the ring with some popular JavaScript-based menu systems.
The first of these is the very popular HIERmenus script (available at http://www.webreference.com/dhtml/hiermenus/ ). This very flexible menu system is completely written in JavaScript, and relies on JavaScript arrays (packaged in a specific format) to build a hierarchical menu tree. I’m not going to get into the nitty-gritty of how it works - there’s some excellent documentation if you’re interested - but rather plan to focus on how this client-side code can be connected to a database of menu items via the Menu class.
Let’s suppose that I wanted to build a menu tree which looked like this:
mysql> SELECT id, label, parent FROM menu;
+----+------------------------+--------+
| id | label | parent |
+----+------------------------+--------+
| 1 | Services | 0 |
| 2 | Company | 0 |
| 3 | Media Center | 0 |
| 4 | Your Account | 0 |
| 5 | Community | 0 |
| 6 | For Content Publishers | 1 |
| 7 | For Small Businesses | 1 |
| 8 | Background | 2 |
| 9 | Clients | 2 |
| 10 | Addresses | 2 |
| 11 | Jobs | 2 |
| 12 | News | 2 |
| 13 | Press Releases | 3 |
| 14 | Media Kit | 3 |
| 15 | Log In | 4 |
| 16 | Columns | 5 |
| 17 | Colophon | 16 |
| 18 | Cut | 16 |
| 19 | Boombox | 16 |
| 20 | The HITG Report | 16 |
| 21 | Trog | 16 |
+----+------------------------+--------+
21 rows in set (0.06 sec)
If I was to build this menu using the HIERmenus system, I would need to manually code a series of JavaScript arrays, like this:
HM_Array1 = [ [180, 200, 50, "black", "white", "white", "black", "black", "gray", 0, 0, 0, 1, 1, 1, "null", "null", ,], ["Services", "http://www.melonfire.com/services/", 1, 0, 1], ["Company", "http://www.melonfire.com/company/", 1, 0, 1], ["Media Center", "http://www.melonfire.com/mcenter/", 1, 0, 1], ["Your Account", "http://www.melonfire.com/account/", 1, 0, 1], ["Community", "http://www.melonfire.com/community/", 1, 0, 1] ];
HM_Array1_1 = [ [180, 200, 50, "black", "white", "white", "black", "black", "gray", 0, 0, 0, 1, 1, 1, "null", "null", ,], ["For Content Publishers", "http://www.melonfire.com/services/content.html", 1, 0, 0], ["For Small Businesses", "http://www.melonfire.com/services/sbs.html", 1, 0, 0] ];
HM_Array1_2 = [ [180, 200, 50, "black", "white", "white", "black", "black", "gray", 0, 0, 0, 1, 1, 1, "null", "null", ,], ["Background", "http://www.melonfire.com/company/background.html", 1, 0, 0], ["Clients", "http://www.melonfire.com/company/clients.html", 1, 0, 0], ["Addresses", "http://www.melonfire.com/company/addresses.html", 1, 0, 0], ["Jobs", "http://www.melonfire.com/company/jobs.html", 1, 0, 0], ["News", "http://www.melonfire.com/company/news.php3", 1, 0, 0] ];
HM_Array1_3 = [ [180, 200, 50, "black", "white", "white", "black", "black", "gray", 0, 0, 0, 1, 1, 1, "null", "null", ,], ["Press Releases", "http://www.melonfire.com/mcenter/pr.html", 1, 0, 0], ["Media Kit", "http://www.melonfire.com/mcenter/mkit.html", 1, 0, 0] ];
HM_Array1_4 = [ [180, 200, 50, "black", "white", "white", "black", "black", "gray", 0, 0, 0, 1, 1, 1, "null", "null", ,], ["Log In", "http://www.melonfire.com/account/index.html", 1, 0, 0] ];
HM_Array1_5 = [ [180, 200, 50, "black", "white", "white", "black", "black", "gray", 0, 0, 0, 1, 1, 1, "null", "null", ,], ["Columns", "http://www.melonfire.com/community/columns/", 1, 0, 1] ];
HM_Array1_5_1 = [ [180, 200, 50, "black", "white", "white", "black", "black", "gray", 0, 0, 0, 1, 1, 1, "null", "null", ,], ["Colophon", "http://www.melonfire.com/community/columns/colophon/", 1, 0, 0], ["Cut", "http://www.melonfire.com/community/columns/cut/", 1, 0, 0], ["Boombox", "http://www.melonfire.com/community/columns/boombox/", 1, 0, 0], ["The HITG Report", "http://www.melonfire.com/community/columns/thr/", 1, 0, 0], ["Trog", "http://www.melonfire.com/community/columns/trog/", 1, 0, 0] ];
The downside here is obvious - each time I want to change the menu structure, I have to go into the JavaScript source and muck about with the arrays (which aren’t exactly all that user-friendly to begin with.) What I would prefer to do, however, is somehow interface my database table to the HIERmenus system, so that updating the menu becomes as simple as executing an SQL query to change the relationships in the mySQL table.
With the help of some clever PHP code and the Menu object I’ve just built, this becomes not just possible, but a snap to accomplish. The first step is to alter the HIERmenus scripts to reference a PHP file for the arrays, rather than a JavaScript file - simply alter the reference to “HM_Arrays.js” in the file “HM_Loader.js” to “HM_Arrays.js.php”, as below.
if(HM_IsMenu) {
document.write("<SCR" + "IPT LANGUAGE='JavaScript1.2' SRC='HM_Arrays.js.php ' TYPE='text/javascript'><\/SCR" + "IPT>");
document.write("<SCR" + "IPT LANGUAGE='JavaScript1.2' SRC='HM_Script"+ HM_BrowserString +".js' TYPE='text/javascript'><\/SCR" + "IPT>");
}
Next, create the script “HM_Arrays.js.php”, and populate it with the following PHP code:
<?php
// options to configure menu appearance
$HM_Menu_Options = "150, 200, 50, \"black\", \"white\", \"white\", \"black\", \"black\", \"gray\", 0, 0, 0, 1, 1, 1, \"null\", \"null\", ,";
// create object
include("menu.class.php");
$obj = new Menu;
// run recursive function
buildMenu(0, "HM_Array1");
// this is a recursive function to generate
// the JS arrays needed by HIERmenus
// this function accepts an id (used to generate children)
// and a prefix string (attached to every array name, needed by HIERmenus)
// for more information on how this works and syntax,
// refer to HIERmenus documentation
function buildMenu($id, $prefix)
{
// obtain a reference to the Menu object
// and also source the options
global $obj;
global $HM_Menu_Options;
// get children of this node
$children = $obj->get_children($id);
// create a JS array with the correct name
$array_str = $prefix . " = [ [" . $HM_Menu_Options . "]";
// iterate through child nodes and create individual array elements
for ($x=0; $x<sizeof($children); $x++)
{
$array_elements_str = ", [\"" . $children[$x]['label'] . "\", \"" . $children[$x]['link'] . "\", 1, 0, " . $obj->get_type($children[$x]['id']) . "]";
// if a child node has further children,
// recurse with appropriate modification to the prefix
if($obj->get_type($children[$x]['id']) == 1)
{
$temp = $prefix . "_" . ($x+1);
buildMenu($children[$x]['id'], $temp);
}
// add the final list of array elements to the main array
$array_str .= $array_elements_str;
}
// close the JS array
$array_str .= " ];";
// and print it
print $array_str;
}
?>
The end result of all this processing: a set of JavaScript arrays containing the various menu nodes, in a format which is acceptable to HIERmenus. This is accomplished by means of a recursive function (conceptually identical to the one used in the print_menu_tree() method) which takes care of iterating through the various levels of the menu tree and printing arrays in the format required by HIERmenus.
Now all we need is a HTML page which activates the HIERmenus system and displays the menu tree in all its glory.
<html>
<head>
<basefont face="Arial">
<script language="JavaScript" type="text/javascript">
<!--
if(window.event + "" == "undefined") event = null;
function HM_f_PopUp(){return false};
function HM_f_PopDown(){return false};
popUp = HM_f_PopUp;
popDown = HM_f_PopDown;
//-->
</script>
</head>
<body>
<a href="#" onMouseOver="popUp('HM_Menu1',event)" onMouseOut="popDown('HM_Menu1')">Roll your mouse over this link to see a menu</a>
<script language="JavaScript1.2" src="HM_Loader.js" type='text/javascript'></script>
</body>
</html>
And here’s what it looks like:
Don’t worry too much about the JavaScript code in this example - it is merely standard code required for HIERmenus to work, and is clearly documented by the developers of the system. The important thing to note here is that we’ve taken a pure client-side application and successfully connected it to a server-side database using standard method calls within the Menu object.
Of course, this solution may not be ideal in every case. Using a database and a PHP script to generate the JavaScript arrays dynamically (rather than storing and using arrays from static JavaScript files) may degrade performance; however, it does offer a benefit from the point of view of simpler maintenance of menu tree relationships. It’s much easier to alter relationships in a mySQL table than it is to open up a JavaScript file and edit the information in it; a non-technical person can easily accomplish the former, but may have difficulty with the latter. Consequently, a thorough cost-benefit analysis should be performed before making a decision as to which option is best suited to a specific case.
Collapsing Inwards
If you go back a few pages, you’ll notice that one of the design goals of this Menu object was to separate the visual presentation of a menu from the relationships between the various nodes. I’ve already demonstrated how the same Menu object can be used to create a directory and a hierarchical menu tree with standard methods.
My third example is similar to the second, again connecting a JavaScript-based menu system to the menu database to dynamically build a menu tree. For this, I plan to use another very popular menu constructor, FolderTree (free version available at http://www.geocities.com/marcelino_martins/foldertree.html ), which uses Windows Explorer-style files and folders to display a hierarchy of items.
First, I need an HTML page to invoke FolderTree and display the menu:
<html>
<head>
<basefont face="Arial">
<link rel="stylesheet" href="ftie4style.css">
<!-- Infrastructure code for the tree -->
<script src="ftiens4.js"></script>
<!-- Execution of the code that actually builds the specific tree -->
<script src="menu.js.php"></script>
<script>
initializeDocument()
</script>
</head>
<body bgcolor=white>
</body>
</html>
As you can see, this file sources “menu.js.php”, which contains the actual menu data. This file is usually created manually as per the user’s requirements; I plan to hook it up to my database table to generate it dynamically. Here’s the code:
// menu.js.php
// set up tree root
foldersTree = gFld("Melonfire", "http://www.melonfire.com");
<?php
// initialize object
include("menu.class.php");
$obj = new Menu();
// counter - used to create tree nodes
$count = 0;
function buildMenu($id, $node)
{
global $obj;
global $count;
// get children
$children = $obj->get_children($id);
for ($x=0; $x<sizeof($children); $x++)
{
// write the name of this node
$newNode = "aux" . $count;
// if folder
if($obj->get_type($children[$x]["id"]) == 1)
{
// call appropriate FolderTree function to display folder icon
$count++;
$str = $newNode . " = insFld(" . $node . ", gFld(\"" . $children[$x]['label'] . "\", \"" . $children[$x]['link'] . "\"));";
print $str;
// and recurse for next level
buildMenu($children[$x]["id"], $newNode);
}
else
{
// if last level
// call appropriate FolderTree function to display file icon
$str = "insDoc(" . $node . ", gLnk(2, \"" . $children[$x]['label'] . "\", \"" . $children[$x]['link'] . "\"));";
print $str;
}
}
}
// initiate recursive function
buildMenu(0, "foldersTree");
?>
You just gotta love those recursive functions!
Here’s what it all looks like:
Extending Yourself
All the examples you’ve seen thus far have used the same standard API defined within the Menu object. However, this assumes one important thing - that a database table (in the format described) has already been created and populated with menu records. In case this is an unreasonable assumption for your specific requirements, you might consider adding a few method calls to add and delete nodes respectively.
<?php
class Menu
{
// other methods
// function: add a record to the menu table
function create_node($label, $link, $parent)
{
$this->query("INSERT INTO $this->table(label, link, parent) VALUES ('$label', '$link', '$parent')");
}
// function: remove a record from the menu table
function remove_node($id)
{
$this->query("DELETE FROM $this->table WHERE id = '$id'");
}
}
?>
You might also want to consider developing a simple administration interface to these method calls, so that users can easily modify the menu tree via a GUI.
And that’s about all for the moment. In this article, you expanded your knowledge of PHP’s OOP capabilities by actually using all that theory to build something useful - a menu widget which can be used to describe the relationships within a hierarchical menu system, independent of how the menu is visually presented.
If you work with menu systems, whether on a Web site, within a Web application or on an embedded system, you might find this object a handy tool in your next development effort. If you’re a novice programmer struggling to understand how OOP can make your life easier, I hope this article offered some pointers, as well as some illustration of how object-oriented programming works. And if you don’t fit into either of those categories - well, I hope you found it interesting and informative, anyway.
See you soon!
Note: All examples in this article have been tested on Linux/i586 with PHP4, HIERmenus 4.0.12, and FolderTree 2.0. HIERmenus and FolderTree copyright their respective authors. Examples are illustrative only, and are not meant for a production environment. YMMV!
This article was first published on 01 Aug 2001.