[racktables-users] My first usability patch - extensive

  • From: "Jonathan Thurman" <jthurman42@xxxxxxxxx>
  • To: racktables-users@xxxxxxxxxxxxx
  • Date: Sun, 8 Jun 2008 20:48:49 -0700

After finding RackTables to be so useful for me at work, I wanted to
help extend the usability of it (so my coworkers would help
document!).  All work was done patching against the trunk, but it
applied cleanly enough against the release version I ported the failed
pieces and created a separate patch.  Please note that my testing of
these patches has been limited, so I don't recommend you install these
in production until you have a chance to test them.  Hopefully you all
find the changes useful, and don't find any issues.  Please let me
know either way!  Thanks

-Jonathan

Attached are two patches:
  usability-20080608-svn1955.patch  -  BETA  For applying to
Subversion 1955 (created with "svn diff" command)
  usability-20080609-0.15.1.patch  -  BETA  For applying to 0.15.1 (it
should apply cleanly, created with "diff -uNr")


Here is a brief list of the changes I made:
---------------------------------------------------------------------------

Rackspace Page
  * Added printing of Messages / Errors
  * "Add more" tab to allow adding multiple Rack Rows

Row Page
  * Add "Delete" tab to Rows with no racks, with confirmation page

Rack Page
  * Add "Delete" tab to Rack with no objects mounted, with confirmation page

Configure Page
  * Hardware Objects
    * Add/Delete/Update Hardware Categories ("HW Type" Dictionary Attributes)
    * Add/Delete/Update Items in above mentioned Categories

Hardware Objects Page
  * New page found under Configuration page
  * Makes managing Hardware categories (Routers, Network Switches, etc) MUCH
    easier and more automated

General Changes
  * Visual adjustment: Make all racks align to the "floor" with each other
    on Rack / Rackspace pages. I didn't like my 40U racks "floating" between
    the 42U racks
  * Changed printImageHREF to include the 'title' attribute for linked images
  * Removed unused variable ($refcnt) in function getDict (inc/database.php)
  * Moved function "delRow" from inc/functions.php to inc/ophandlers.php
    Rewrote delRow breaking it out to many functions
  * Moved function "delRack" from inc/functions.php to inc/ophandlers.php
    Rewrote delRack breaking it out to many functions
Index: inc/navigation.php
===================================================================
--- inc/navigation.php  (revision 1955)
+++ inc/navigation.php  (working copy)
@@ -18,12 +18,15 @@
 $page['rackspace']['parent'] = 'index';
 $tab['rackspace']['default'] = 'Browse';
 $tab['rackspace']['history'] = 'History';
+$tab['rackspace']['addmore'] = 'Add more';                     // JT: 
2008-06-08 Add rows
 $tab['rackspace']['firstrow'] = 'Click me!';
 $trigger['rackspace']['firstrow'] = 'trigger_emptyRackspace';
 $tabhandler['rackspace']['default'] = 'renderRackspace';
 $tabhandler['rackspace']['history'] = 'renderRackspaceHistory';
+$tabhandler['rackspace']['addmore'] = 'renderNewRowForm';      // JT: 
2008-06-08 Add rows
 $tabhandler['rackspace']['firstrow'] = 'renderFirstRowForm';
 $tabextraclass['rackspace']['firstrow'] = 'attn';
+$ophandler['rackspace']['addmore']['add'] = 'addRow';          // JT: 
2008-06-08 Add Rows
 
 $page['objects']['title'] = 'Objects';
 $page['objects']['parent'] = 'index';
@@ -39,10 +42,14 @@
 $tab['row']['default'] = 'View';
 $tab['row']['newrack'] = 'Add new rack';
 $tab['row']['tagroller'] = 'Tag roller';
+$tab['row']['del'] = 'Delete';                         // JT: 2008-06-08 
Delete empty row
 $tabhandler['row']['default'] = 'renderRow';
 $tabhandler['row']['newrack'] = 'renderNewRackForm';
 $tabhandler['row']['tagroller'] = 'renderTagRollerForRow';
+$tabhandler['row']['del'] = 'renderDeleteRowForm';     // JT: 2008-06-08 
Delete empty row
+$trigger['row']['del'] = 'trigger_delRowForm';         // JT: 2008-06-08 
Delete empty row
 $ophandler['row']['tagroller']['rollTags'] = 'rollTags';
+$ophandler['row']['del']['del'] = 'delRow';            // JT: 2008-06-08 
Delete empty row
 
 $page['rack']['title_handler'] = 'dynamic_title_rack';
 $page['rack']['bypass'] = 'rack_id';
@@ -55,13 +62,17 @@
 $tab['rack']['design'] = 'Design';
 $tab['rack']['problems'] = 'Problems';
 $tab['rack']['tags'] = 'Tags';
+$tab['rack']['del'] = 'Delete';                                // JT: 
2008-06-08 Easily delete rack tab
 $tabhandler['rack']['default'] = 'renderRackPage';
 $tabhandler['rack']['edit'] = 'renderEditRackForm';
 $tabhandler['rack']['design'] = 'renderRackDesign';
 $tabhandler['rack']['problems'] = 'renderRackProblems';
 $tabhandler['rack']['tags'] = 'renderRackTags';
+$tabhandler['rack']['del'] = 'renderDeleteRackForm';   // JT: 2008-06-08 
Easily delete rack handler
 $trigger['rack']['tags'] = 'trigger_tags';
+$trigger['rack']['del'] = 'trigger_delRackForm';       // JT: 2008-06-08 
Easily delete rack trigger
 $ophandler['rack']['tags']['save'] = 'saveRackTags';
+$ophandler['rack']['del']['del'] = 'delRack';          // JT: 2008-06-08 
Easily delete rack handler
 
 $page['objgroup']['title_handler'] = 'dynamic_title_objgroup';
 $page['objgroup']['handler'] = 'renderObjectGroup';
@@ -337,6 +348,25 @@
 $ophandler['tagtree']['edit']['createTag'] = 'createTag';
 $ophandler['tagtree']['edit']['updateTag'] = 'updateTag';
 
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// the hwobjs page allows for easier editing of HW Type Dictionary Attributes
+// without having to go through the manual process of adding/udpdating/deleting
+// each required peice
+$page['hwobjs']['title'] = 'Hardware Objects';
+$page['hwobjs']['parent'] = 'config';
+$tab['hwobjs']['default'] = 'Categories';
+$tab['hwobjs']['items'] = 'Items';
+$tabhandler['hwobjs']['default'] = 'renderHWObjCategories';
+$tabhandler['hwobjs']['items'] = 'renderHWObjItems';
+$ophandler['hwobjs']['default']['add'] = 'supplementObjectCategories';
+$ophandler['hwobjs']['default']['del'] = 'reduceObjectCategories';
+$ophandler['hwobjs']['default']['upd'] = 'updateObjectCategories';
+$ophandler['hwobjs']['items']['add'] = 'supplementObjectItems';
+$ophandler['hwobjs']['items']['del'] = 'reduceObjectItems';
+$ophandler['hwobjs']['items']['upd'] = 'updateObjectItems';
+
+
 $page['reports']['title'] = 'Reports';
 $page['reports']['parent'] = 'index';
 $page['reports']['handler'] = 'renderReportSummary';
Index: inc/ophandlers.php
===================================================================
--- inc/ophandlers.php  (revision 1955)
+++ inc/ophandlers.php  (working copy)
@@ -1373,4 +1373,283 @@
        return "${root}?page=${pageno}&tab=${tabno}&row_id=${row_id}&message=" 
. urlencode ("${nnew} new records done, ${ndupes} already existed");
 }
 
+//
+//  Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Function to delete a rack, purge from database if requested
+//
+function delRack ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg('rack_id');       // Rack to delete
+       $rack_id = $_REQUEST['rack_id'];
+       $purge   = FALSE;
+
+       // Get my Row ID so we can return to that row
+       $rack = getRackData($rack_id, TRUE);
+       $row_id = $rack['row_id'];
+
+       // Attempt delete, if successful, return to row, if failed, return to 
my rack
+       if (isset($_REQUEST['purge']))
+               $purge = TRUE;
+
+       if (commitDeleteRack ($rack_id, $purge) === TRUE)
+       {
+               recordHistory ('Rack', "id = ${rack_id}");
+               return "${root}?page=row&row_id=${row_id}&message=" . urlencode 
('Rack deleted.');
+       }
+       else
+               return "${root}?page=${pageno}&rack_id=${rack_id}&error=" . 
urlencode ("Deletion failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Add rows, supports multiple at one time
+//
+function addRow()
+{
+       global $root, $pageno, $tabno;
+       assertStringArg ('row_names', __FUNCTION__, TRUE);
+       $message = "";
+       $error = "";
+
+       // copy-and-paste from renderAddMultipleObjectsForm()
+       $names1 = explode ('\n', $_REQUEST['row_names']);
+       $names2 = array();
+       foreach ($names1 as $line)
+       {
+               $parts = explode ('\r', $line);
+               reset ($parts);
+               if (empty ($parts[0]))
+                       continue;
+               else
+                       $names2[] = rtrim ($parts[0]);
+       }
+       foreach ($names2 as $cname)
+               if (commitSupplementDictionary ( 3, $cname) === TRUE)
+                       $message .= " '${cname}'";
+               else
+                       $error .= " '${cname}'";
+
+       return "${root}?page=rackspace" .
+               (empty($message) ? "" : "&message=" . urlencode ("Added row(s): 
" . $message)) .
+               (empty($error) ? "" : "&error=" . urlencode ("Failed to add 
row(s): " . $error));
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Delete the row from the Dictionary
+//
+function delRow ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg('row_id', __FUNCTION__);
+       $row_id = $_REQUEST['row_id'];
+
+       if (commitDeleteRow ($row_id) === TRUE)
+       {
+               recordHistory ('RackRow', "id = ${row_id}");
+               return "${root}?page=rackspace&message=" . urlencode ('Row 
deleted.');
+       }
+       else
+               return "${root}?page=${pageno}&row_id=${row_id}&error=" . 
urlencode ("Deletion failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Changes names in Dictionary and Chapter
+// Copied from updateDictionary
+//
+// RackObjectType hardcoded...
+//
+function updateObjectCategories ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg ('chapter_no', __FUNCTION__);
+       assertUIntArg ('dict_key', __FUNCTION__);
+       assertStringArg ('dict_value', __FUNCTION__);
+       $rack_obj_chapter = 1;  // RackObjectType
+       $chapter_name = trim($_REQUEST['dict_value'] . " Objects");
+
+       if (commitUpdateDictionary ($rack_obj_chapter, $_REQUEST['dict_key'], 
$_REQUEST['dict_value']) === TRUE)
+               if (commitUpdateChapter ($_REQUEST['chapter_no'], 
$chapter_name) === TRUE)
+                       return "${root}?page=${pageno}&tab=${tabno}&message=" . 
urlencode ('Update succeeded.');
+               else
+                       return "${root}?page=${pageno}&tab=${tabno}&error=" . 
urlencode ("Update failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// This puts all of the steps needed to create a new object category:
+//  - Create Chapter, Add to RackObjectType Chapter, Add to AttribureMap 
(based on attr_id) 
+//
+// Has RackObjectType hardcoded...
+// Initially tested only with HW Type (attr_id = 2) but should work for any 
Dictionary Attribute
+//
+function supplementObjectCategories ()
+{
+       global $root, $pageno, $tabno;
+       $status = FALSE;        // Set this on success
+       assertStringArg ('objtype_name', __FUNCTION__);
+       assertUIntArg ('attr_id', __FUNCTION__);
+       $rack_obj_chapter = 1;  // RackObjectType
+       $attr_id = $_REQUEST['attr_id'];
+       $objtype_name = trim($_REQUEST['objtype_name']);
+       $chapter_name = "$objtype_name Objects";
+       $chapter_no   = 0;
+       $dict_key     = 0;
+
+       if (commitAddChapter ($chapter_name) === TRUE)
+       {
+               // Find the inserted chapter number
+               $dict = getDict();
+               foreach ($dict as $chapter)
+                       if (strcmp($chapter['name'], $chapter_name) === 0)
+                       {
+                               $chapter_no = $chapter['no'];
+                               break;
+                       }
+
+               if ($chapter_no != 0)
+               {
+                       if (commitSupplementDictionary ($rack_obj_chapter, 
$objtype_name) === TRUE)
+                       {
+                               $objtypes = getObjectTypeList();
+                               foreach ($objtypes as $id => $name)
+                                       if (strcmp($name, $objtype_name) === 0)
+                                       {
+                                               $objtype_id = $id;
+                                               break;
+                                       }
+                               
+                               if ($objtype_id != 0)
+                                       if (commitSupplementAttrMap ($attr_id, 
$objtype_id, $chapter_no) === TRUE)
+                                               $status = TRUE;
+                       }
+               }
+       }
+
+       // Return messages
+       if ($status)
+               return "${root}?page=${pageno}&tab=${tabno}&message=" . 
urlencode ('Supplement succeeded.');
+       else
+               return "${root}?page=${pageno}&tab=${tabno}&error=" . urlencode 
("Supplement failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// This puts all of the steps needed to remove a new object category:
+//  - Remove from AttributeMap, Remove from Chapter RackObjectType, Remove 
Chapter
+//
+// RackObjectType is hardcoded...
+// tested only with HW Type (attr_id = 2) but should work with any Dictionary 
Attribute type
+//
+function reduceObjectCategories ()
+{
+       global $root, $pageno, $tabno;
+       $status = FALSE;        // Set this on success
+       assertUIntArg ('objtype_id', __FUNCTION__);
+       assertUIntArg ('chapter_no', __FUNCTION__);
+       assertUIntArg ('attr_id', __FUNCTION__);
+       $rack_obj_chapter = 1;  // RackObjectType
+
+
+       if (commitReduceAttrMap ($_REQUEST['attr_id'], $_REQUEST['objtype_id']) 
=== TRUE)
+               if (commitReduceDictionary ($rack_obj_chapter, 
$_REQUEST['objtype_id']) === TRUE)
+                       if (commitDeleteChapter ($_REQUEST['chapter_no']))
+                               return 
"${root}?page=${pageno}&tab=${tabno}&message=" . urlencode ('Reduce 
succeeded.');
+                       else
+                               return 
"${root}?page=${pageno}&tab=${tabno}&error=" . urlencode ("Reduce failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Add objects to the Dictionary for a Dictionary type Attribute
+// This handles puting the Manufacturer, model, and url into the Wiki format
+// used to store the data.
+// Leaves the chapter number in the url, needed for my easy item editor
+//
+function supplementObjectItems ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg('chapter_no', __FUNCTION__);
+       assertStringArg ('man', __FUNCTION__);
+       assertStringArg ('mod', __FUNCTION__, TRUE);
+       assertStringArg ('url', __FUNCTION__, TRUE);
+       $desc = trim($_REQUEST['man']);
+
+       // Rebuild the Description
+       if (!empty($_REQUEST['mod']))
+               $desc .= "%GPASS%" . trim($_REQUEST['mod']);
+
+       // Put description and url back together, if there is a url
+       if (!empty($_REQUEST['url']))
+               $dict_value = "[[ " . $desc . " | " . trim($_REQUEST['url']) . 
" ]]";
+       else
+               $dict_value = $desc;
+
+       if (commitSupplementDictionary ($_REQUEST['chapter_no'], $dict_value) 
=== TRUE)
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&message="
 . urlencode ('Add succeeded.');
+       else
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&error="
 . urlencode ("Add failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Remove objects from Dictionary
+// Leaves the chapter number in the url, needed for my easy item editor
+//
+function reduceObjectItems ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg ('chapter_no', __FUNCTION__);
+       assertUIntArg ('dict_key', __FUNCTION__);
+
+       if (commitReduceDictionary ($_REQUEST['chapter_no'], 
$_REQUEST['dict_key']))
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&message="
 . urlencode ('Reduce succeeded.');
+       else
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&error="
 . urlencode ("Reduce failed!");
+}
+
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Update objects in Dictionary
+//  - Handles adding %GPASS% when sorting by Manufacturer
+//  - Handled Wiki encoding Manufacturer, Model, and URL
+//
+function updateObjectItems ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg ('chapter_no', __FUNCTION__);
+       assertUIntArg ('dict_key', __FUNCTION__);
+       assertStringArg ('man', __FUNCTION__);
+       assertStringArg ('mod', __FUNCTION__, TRUE);
+       assertStringArg ('url', __FUNCTION__, TRUE);
+       $desc = trim($_REQUEST['man']);
+
+       // Rebuild the Description
+       if (!empty($_REQUEST['mod']))
+               $desc .= "%GPASS%" . trim($_REQUEST['mod']);
+
+       // Put these back together (Wiki encode)
+       if (!empty($_REQUEST['url']))
+               $dict_value = "[[ " . $desc . " | " . trim($_REQUEST['url']) . 
" ]]";
+       else
+               $dict_value = $desc;
+
+       if (commitUpdateDictionary ($_REQUEST['chapter_no'], 
$_REQUEST['dict_key'], $dict_value) === TRUE)
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&message="
 . urlencode ('Update succeeded.');
+       else
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&error="
 . urlencode ("Update failed!");
+}
+
 ?>
Index: inc/interface.php
===================================================================
--- inc/interface.php   (revision 1955)
+++ inc/interface.php   (working copy)
@@ -61,6 +61,7 @@
 {
        $tagfilter = getTagFilter();
        $tagfilter_str = getTagFilterStr ($tagfilter);
+       showMessageOrError();   // JT: 2008-06-08 Show messages / errors here 
too
        echo "<table class=objview border=0 width='100%'><tr><td class=pcleft>";
        renderTagFilterPortlet ($tagfilter, 'rack');
        echo '</td><td class=pcright>';
@@ -79,7 +80,7 @@
                echo "<td><table border=0 cellspacing=5><tr>";
                foreach ($rackList as $rack)
                {
-                       echo "<td align=center><a 
href='${root}?page=rack&rack_id=${rack['id']}'>";
+                       echo "<td align=center valign=bottom><a 
href='${root}?page=rack&rack_id=${rack['id']}'>";       // JT: 2008-06-08 
Vertical align the racks to the bottom
                        echo "<img border=0 width=${rackwidth} height=";
                        echo 3 + 3 + $rack['height'] * 2;
                        echo " title='${rack['height']} units'";
@@ -131,7 +132,7 @@
        echo "<table border=0 cellspacing=5 align='center'><tr>";
        foreach ($rackList as $rack)
        {
-               echo "<td align=center class=row_${order}><a 
href='${root}?page=rack&rack_id=${rack['id']}'>";
+               echo "<td align=center valign=bottom class=row_${order}><a 
href='${root}?page=rack&rack_id=${rack['id']}'>";    // JT: 2008-06-08 Vertical 
align the racks to the bottom
                echo "<img border=0 width=" . $rackwidth * getConfigVar 
('ROW_SCALE') . " height=";
                echo (3 + 3 + $rack['height'] * 2) * getConfigVar ('ROW_SCALE');
                echo " title='${rack['height']} units'";
@@ -529,6 +530,40 @@
        finishPortlet();
 }
 
+//
+//  Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Updated way to delete a rack, as the delRack in inc/functions.php was
+// not working correctly
+//
+function renderDeleteRackForm ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg ('rack_id');
+
+       $rack_id = $_REQUEST['rack_id'];
+
+       startPortlet ('Confirm Rack Deletion');
+       echo "<form action='${root}process.php' method=post>";
+       echo "<input type=hidden name=page value='${pageno}'>";
+       echo "<input type=hidden name=tab value='${tabno}'>";
+       echo "<input type=hidden name=op value='del'>";
+       echo "<input type=hidden name=rack_id value='${rack_id}'>";
+
+       echo "<table border=0 cellspacing=0 cellpadding=3 width='100%'>";
+       echo "<tr><td>";
+       echo "Deleting a rack marks it as deleted in the database.<br>";
+       echo "If you want it removed completely, check the Purge box.<br><br>";
+       echo "Are you sure you want to delete this rack?";
+       echo "</td></tr>";
+       echo "<tr><td><input type=checkbox name=purge value=0>Purge from 
database<br>&nbsp;</td></tr>";
+       echo "<tr><td><input type=submit value='Delete this Rack'></td></tr>";
+       echo "</table>";
+       echo "</form>";
+       finishPortlet();
+}
+
+
 // This is a helper for creators and editors.
 function printSelect ($rowList, $select_name, $selected_id = 1)
 {
@@ -3497,6 +3532,7 @@
                        "src='${root}${img['path']}' " .
                        "border=0 " .
                        ($tabindex ? '' : "tabindex=${tabindex}") .
+                       (empty ($title) ? '' : " title='${title}'") .   // JT: 
Add title to input hrefs too
                        ">";
        else
                echo
@@ -5014,4 +5050,252 @@
        echo '</pre>';
 }
 
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Form to allow creating multiple rows from the Rackspace page
+//
+function renderNewRowForm ()
+{
+       global $root, $pageno, $tabno;
+
+       echo "<table border=0 width='100%'><tr><td valign=top>";
+       // Render a form for the next.
+       startPortlet ('Add Rows');
+       echo "<form action='${root}process.php' method=post>";
+       echo "<input type=hidden name=page value=${pageno}>";
+       echo "<input type=hidden name=tab value=${tabno}>";
+       echo "<input type=hidden name=op value='add'>";
+
+       echo '<table border=0 align=center>';
+       echo "<tr><th class=center>Enter row names, one per line:</th></tr>\n";
+        echo "<tr><td class=center><textarea name=row_names cols=40 
rows=15></textarea></td></tr>\n";
+       echo "<tr><td class=submit><input type=submit name=got_mdata 
value='Add'></td></tr>\n";
+       echo '</form></table>';
+       finishPortlet();
+       echo '</td></tr>';
+       echo '</table>';
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Form to delete an empty row
+//
+function renderDeleteRowForm ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg ('row_id');
+
+       $row_id = $_REQUEST['row_id'];
+
+       startPortlet ('Confirm Row Deletion');
+       echo "<form action='${root}process.php' method=post>";
+       echo "<input type=hidden name=page value='${pageno}'>";
+       echo "<input type=hidden name=tab value='${tabno}'>";
+       echo "<input type=hidden name=op value='del'>";
+       echo "<input type=hidden name=row_id value='${row_id}'>";
+
+       echo "<table border=0 cellspacing=0 cellpadding=3 width='100%'>";
+       echo "<tr><td>";
+       echo "You are about to delete this row from the database.<br>";
+       echo "This action can NOT be undone!<br>";
+       echo "Are you sure you want to delete this row?";
+       echo "</td></tr>";
+       echo "<tr><td><input type=submit value='Delete this Row'></td></tr>";
+       echo "</table>";
+       echo "</form>";
+       finishPortlet();
+
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Create a form for managing HW Type dictionary objects
+// I Call these Hardware Categories, but basically "HW Type" Dictionary 
Attribute editing
+//
+function renderHWObjCategories ()
+{
+       global $root, $pageno, $tabno;
+       $attrMap = getObjectCategories(2);      // HW Type
+       showMessageOrError();
+       startPortlet ('Hardware Categories');
+       echo "<table cellspacing=0 cellpadding=5 align=center 
class=widetable>\n";
+       echo '<tr align=left><th>&nbsp;</th><th>Category</th><th>Item 
Count</th><th>&nbsp;</th></tr>';
+
+       foreach ($attrMap as $attr)
+       {
+               echo '<tr><td>';
+               if ($attr['objcount'])
+                       printImageHREF ('nodelete', 'contains ' . 
$attr['objcount'] . ' item(s)');
+               else
+               {
+                       echo "<form action='${root}process.php' method=post>";
+                       echo "<input type=hidden name=page value='${pageno}'>";
+                       echo "<input type=hidden name=tab value='${tabno}'>";
+                       echo "<input type=hidden name=op value=del>";
+                       echo "<input type=hidden name=attr_id value='2'>";      
// HW Type
+                       echo "<input type=hidden name=objtype_id 
value='${attr['id']}'>";
+                       echo "<input type=hidden name=chapter_no 
value='${attr['chapter_no']}'>";
+                       printImageHREF ('delete', '', TRUE);
+                       echo '</form>';
+               }
+               echo '</td>';
+               echo "<form action='${root}process.php' method=post>";
+               echo "<input type=hidden name=page value='${pageno}'>";
+               echo "<input type=hidden name=tab value='${tabno}'>";
+               echo "<input type=hidden name=op value=upd>";
+               echo "<input type=hidden name=dict_key value='${attr['id']}'>";
+               echo "<input type=hidden name=chapter_no 
value='${attr['chapter_no']}'>";
+               echo "<td align=left>";
+               echo "<input type=text name=dict_value size=20 
value='${attr['name']}'>";
+               echo "</td>";
+               echo "<td><a 
href='${root}?page=hwobjs&tab=items&objtype_id=${attr['chapter_no']}' 
title='Edit Items...'>${attr['objcount']}</a></td>";
+               echo "<td>";
+               printImageHREF ('save', 'Save category', TRUE);
+               echo "</form>";
+               echo "</td></tr>\n";
+       }
+       echo "<form action='${root}process.php' method=post>";
+       echo "<input type=hidden name=page value='${pageno}'>";
+       echo "<input type=hidden name=tab value='${tabno}'>";
+       echo "<input type=hidden name=op value=add>";
+       echo "<input type=hidden name=attr_id value='2'>";      // HW Type
+       echo '<tr><td>';
+       printImageHREF ('add', '', TRUE);
+       echo '<td align=left colspan=3>';
+       echo "<input type=text size=20 name=objtype_name>";
+       echo '</tr>';
+       echo '</form>';
+       echo "</table>\n";
+       finishPortlet();
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Display a page to edit Hardware items by category.
+//
+// attr_id hardcoded... to "HW Type" (2)
+//
+function renderHWObjItems ()
+{
+       global $root, $pageno, $tabno;
+       $attr_id = 2;   // HW Type
+
+       if (isset($_REQUEST['objtype_id']))
+               $objtype_id = $_REQUEST['objtype_id'];
+       else
+               $objtype_id = 0;
+
+       $dict = getDict();              // Get all Items
+       $categories = getObjectCategories($attr_id);
+       showMessageOrError();
+       startPortlet ('Select Category');
+
+       // Find all categories we want allow the user to choose
+       $sel = array();
+       foreach ($categories as $id => $info)
+               $sel[$info['chapter_no']] = $info['name'];
+
+       // Form to choose the category of items to edit
+       echo "<table cellspacing=0 cellpadding=5 align=center>\n";
+       echo "<form action='${root}' method=get>";
+       echo "<input type=hidden name=page value='${pageno}'>";
+       echo "<input type=hidden name=tab value='${tabno}'>";
+       echo '<tr><td valign=top>';
+       printSelect ($sel, 'objtype_id', $objtype_id);
+       echo "</td><td>";
+       echo "<input type=submit value=Select>";        
+       echo "</form>";
+       echo '</td></tr></table>';
+       finishPortlet();
+
+       // Find the selected chapter
+       if (isset($dict[$objtype_id]))
+       {
+               $chapter = $dict[$objtype_id];
+               startPortlet('Category Items');
+               echo "<table cellspacing=0 cellpadding=5 align=center 
class=widetable>\n";
+               echo 
"<tr><th>&nbsp;</th><th>Manufacturer</th><th>Model</th><th>URL</th><th>&nbsp;</th></tr>";
+
+               // Form to Add new item
+               echo "<form action='${root}process.php' method=post>";
+               echo "<input type=hidden name=page value='${pageno}'>";
+               echo "<input type=hidden name=tab value='${tabno}'>";
+               echo "<input type=hidden name=op value='add'>";
+               echo "<input type=hidden name=chapter_no value='$objtype_id'>";
+               echo "<tr>";
+               echo "<td>&nbsp;</td>";
+               echo "<td><input type=text name=man size=20></td>";
+               echo "<td><input type=text name=mod size=20></td>";
+               echo "<td><input type=text name=url size=30></td>";
+               echo "<td>";
+               printImageHREF ('add', 'Add new', TRUE);
+               echo "</td>";
+               echo "</tr>";
+               echo "</form>";
+
+               // this needs to lookup the items for that category
+               foreach ($chapter['word'] as $key => $value)
+               {
+                       // Split the Description / URL (from parseWikiLink)
+                       if (preg_match ('/^\[\[.+\]\]$/', $value))
+                       {
+                               $value = preg_replace ('/^\[\[(.+)\]\]$/', 
'$1', $value);
+                               $s = explode ('|', $value);
+                               $desc = trim ($s[0]);
+                               $url = trim ($s[1]);
+                       }
+                       else
+                       {
+                               $desc = $value;
+                               $url  = "";
+                       }
+
+                       if (preg_match('%GPASS%', $desc))
+                       {
+                               $s = explode ('%GPASS%', $desc, 2);
+                               $manufacturer = $s[0];
+                               $model = $s[1];
+                       }
+                       else
+                       {
+                               $manufacturer = $desc;
+                               $model = "";
+                       }                       
+                       // Print it out
+                       echo "<form action='${root}process.php' method=post>";
+                       echo "<input type=hidden name=page value='${pageno}'>";
+                       echo "<input type=hidden name=tab value='${tabno}'>";
+                       echo "<input type=hidden name=op value='upd'>";
+                       echo "<input type=hidden name=objtype_id 
value='$chapter'>";
+                       echo "<input type=hidden name=chapter_no 
value='${chapter['no']}'>";
+                       echo "<input type=hidden name=dict_key value='${key}'>";
+                       echo "<tr><td>";
+                       // Prevent deleting words currently used somewhere.
+                       if ($chapter['refcnt'][$key])
+                               printImageHREF ('nodelete', 'referenced ' . 
$chapter['refcnt'][$key] . ' time(s)');
+                       else
+                       {
+                               echo "<a 
href='${root}process.php?page=${pageno}&tab=${tabno}&op=del&chapter_no=${chapter['no']}&dict_key=${key}'>";
+                               printImageHREF ('delete', 'Delete word');
+                               echo "</a>";
+                       }
+                       echo '</td>';
+                       echo "<td><input type=text name=man size=20 
value='${manufacturer}'></td>";
+                       echo "<td><input type=text name=mod size=20 
value='${model}'></td>";
+                       echo "<td class=tdright><input type=text name=url 
size=30 value='${url}'></td>";
+                       echo "<td>";
+                       printImageHREF ('save', 'Save item', TRUE);
+                       echo "</td>";
+                       echo "</tr></form>\n";
+               }
+               echo "</table></td>";
+               finishPortlet();
+       }
+}
+
+
 ?>
Index: inc/database.php
===================================================================
--- inc/database.php    (revision 1955)
+++ inc/database.php    (working copy)
@@ -348,6 +348,62 @@
        return recordHistory ('Rack', "id = ${last_insert_id}");
 }
 
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Mark rack as deleted in DB, or purge it
+//
+function commitDeleteRack ($rack_id = 0, $purge = FALSE)
+{
+       if ($rack_id <= 0)
+       {
+               showError ('Invalid args', __FUNCTION__);
+               die;
+       }
+       global $dbxlink;
+       if ($purge === TRUE)
+               $query = "delete from Rack where id=${rack_id} limit 1";
+       else
+               $query =
+                       "update Rack set deleted = 'yes' where id=${rack_id} 
limit 1";
+       $result = $dbxlink->query ($query);
+       if ($result == NULL)
+       {
+               showError ('SQL query failed', __FUNCTION__);
+               die;
+       }
+       return TRUE;
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Delete a row from the database (Rack Row, not a DB row...)
+//
+// Hardcoded RackRow = 3 (Chapter number for RackRow Dictionary)
+//
+function commitDeleteRow ($row_id = 0)
+{
+       $row_chapter_no = 3;    // Hardcoded chapter number (RackRow)
+
+       if ($row_id == 0)
+       {
+               showError ('Not all required args are present.', __FUNCTION__);
+               return;
+       }
+
+       global $dbxlink;
+       $query =
+               "delete from Dictionary where chapter_no=${row_chapter_no} and 
dict_key=${row_id}";
+       $result = $dbxlink->query ($query);
+       if ($result == NULL)
+       {
+               showError ('SQL query failed', __FUNCTION__);
+               die;
+       }
+       return TRUE;
+}
+
 function commitAddObject ($new_name, $new_label, $new_barcode, $new_type_id, 
$new_asset_no, $taglist = array())
 {
        global $dbxlink;
@@ -1348,7 +1404,6 @@
                "where attr_type = 'dict' group by a.attr_id, am.chapter_no, 
uint_value " .
                "order by a.attr_id, am.chapter_no, uint_value";
        $result = useSelectBlade ($query2, __FUNCTION__);
-       $refcnt = array();
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
                $dict[$row['chapter_no']]['refcnt'][$row['uint_value']] = 
$row['refcnt'];
        $result->closeCursor();
@@ -2635,6 +2690,40 @@
        );
 }
 
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// This function collects information about the Categories for the Dictionary 
type Attribute
+// specified by attr_id
+//
+function getObjectCategories ($attr_id)
+{
+       $query =
+               "select am.objtype_id, d1.dict_value as objtype_name, 
am.chapter_no, d2.objcount " .
+               "from Attribute as a natural left join AttributeMap as am " .
+               "left join Dictionary as d1 on am.objtype_id = d1.dict_key " .
+               "left join Chapter as c1 on d1.chapter_no = c1.chapter_no " .
+               "left join Chapter as c2 on am.chapter_no = c2.chapter_no " .
+               "left join (select chapter_no, count(dict_key) as objcount from 
Dictionary group by chapter_no) as d2 on c2.chapter_no = d2.chapter_no " .
+               "where am.attr_id = $attr_id order by objtype_name";
+
+       $result = useSelectBlade ($query);
+       $ret = array();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+       {
+               if ($row['objtype_id'] == '')
+                       continue;
+               $objtype_id = $row['objtype_id'];
+               $ret[$objtype_id]['id'] = $row['objtype_id'];
+               $ret[$objtype_id]['name'] = $row['objtype_name'];
+               $ret[$objtype_id]['chapter_no'] = $row['chapter_no'];
+               $ret[$objtype_id]['objcount'] = $row['objcount'];
+       }
+       $result->closeCursor();
+       return $ret;
+}
+
+
 function createIPv4Prefix ($range = '', $name = '', $is_bcast = FALSE, 
$taglist = array())
 {
        // $range is in x.x.x.x/x format, split into ip/mask vars
Index: inc/triggers.php
===================================================================
--- inc/triggers.php    (revision 1955)
+++ inc/triggers.php    (working copy)
@@ -102,4 +102,32 @@
        return count ($taglist) > 0;
 }
 
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// If the rack is empty, display the delete tab
+//
+function trigger_delRackForm ()
+{
+       assertUIntArg ('rack_id', __FUNCTION__);
+       $rackData = getRackData ($_REQUEST['rack_id'], TRUE);
+       if (getRSUforRack($rackData) === 0)
+               return TRUE;
+       return FALSE;
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// If the row is empty, display the delete tab
+//
+function trigger_delRowForm ()
+{
+       assertUIntArg('row_id', __FUNCTION__);
+       $rowData = getRacksForRow($_REQUEST['row_id']);
+       if (empty($rowData))
+               return TRUE;
+       return FALSE;
+}
+
 ?>
Index: inc/functions.php
===================================================================
--- inc/functions.php   (revision 1955)
+++ inc/functions.php   (working copy)
@@ -153,56 +153,6 @@
        return TRUE;
 }
 
-function delRow ($row_id = 0)
-{
-       if ($row_id == 0)
-       {
-               showError ('Not all required args are present.', __FUNCTION__);
-               return;
-       }
-       if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
-       {
-               echo "Press <a 
href='?op=del_row&row_id=${row_id}&confirmed=true'>here</a> to confirm rack row 
deletion.";
-               return;
-       }
-       global $dbxlink;
-       echo 'Deleting rack row information: ';
-       $result = $dbxlink->query ("update RackRow set deleted = 'yes' where 
id=${row_id} limit 1");
-       if ($result->rowCount() != 1)
-       {
-               showError ('Marked ' . $result.rowCount() . ' rows as deleted, 
but expected 1', __FUNCTION__);
-               return;
-       }
-       echo 'OK<br>';
-       recordHistory ('RackRow', "id = ${row_id}");
-       echo "Information was deleted. You may return to <a 
href='?op=list_rows&editmode=on'>rack row list</a>.";
-}
-
-function delRack ($rack_id = 0)
-{
-       if ($rack_id == 0)
-       {
-               showError ('Not all required args are present.', __FUNCTION__);
-               return;
-       }
-       if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
-       {
-               echo "Press <a 
href='?op=del_rack&rack_id=${rack_id}&confirmed=true'>here</a> to confirm rack 
deletion.";
-               return;
-       }
-       global $dbxlink;
-       echo 'Deleting rack information: ';
-       $result = $dbxlink->query ("update Rack set deleted = 'yes' where 
id=${rack_id} limit 1");
-       if ($result->rowCount() != 1)
-       {
-               showError ('Marked ' . $result.rowCount() . ' rows as deleted, 
but expected 1', __FUNCTION__);
-               return;
-       }
-       echo 'OK<br>';
-       recordHistory ('Rack', "id = ${rack_id}");
-       echo "Information was deleted. You may return to <a 
href='?op=list_racks&editmode=on'>rack list</a>.";
-}
-
 function delObject ($object_id = 0)
 {
        if ($object_id == 0)
diff -uNr RackTables-0.15.1/inc/database.php RackTables/inc/database.php
--- RackTables-0.15.1/inc/database.php  2008-06-05 03:38:09.000000000 -0700
+++ RackTables/inc/database.php 2008-06-08 20:24:02.000000000 -0700
@@ -67,6 +67,40 @@
        return readChapter ('RackObjectType');
 }
 
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// This function collects information about the Categories for the Dictionary 
type Attribute
+// specified by attr_id
+//
+function getObjectCategories ($attr_id)
+{
+       $query =
+               "select am.objtype_id, d1.dict_value as objtype_name, 
am.chapter_no, d2.objcount " .
+               "from Attribute as a natural left join AttributeMap as am " .
+               "left join Dictionary as d1 on am.objtype_id = d1.dict_key " .
+               "left join Chapter as c1 on d1.chapter_no = c1.chapter_no " .
+               "left join Chapter as c2 on am.chapter_no = c2.chapter_no " .
+               "left join (select chapter_no, count(dict_key) as objcount from 
Dictionary group by chapter_no) as d2 on c2.chapter_no = d2.chapter_no " .
+               "where am.attr_id = $attr_id order by objtype_name";
+
+       $result = useSelectBlade ($query);
+       $ret = array();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+       {
+               if ($row['objtype_id'] == '')
+                       continue;
+               $objtype_id = $row['objtype_id'];
+               $ret[$objtype_id]['id'] = $row['objtype_id'];
+               $ret[$objtype_id]['name'] = $row['objtype_name'];
+               $ret[$objtype_id]['chapter_no'] = $row['chapter_no'];
+               $ret[$objtype_id]['objcount'] = $row['objcount'];
+       }
+       $result->closeCursor();
+       return $ret;
+}
+
+
 // Return a part of SQL query suitable for embeding into a bigger text.
 // The returned result should list all tag IDs shown in the tag filter.
 function getWhereClause ($tagfilter = array())
@@ -365,6 +399,62 @@
        return recordHistory ('Rack', "id = ${last_insert_id}");
 }
 
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Mark rack as deleted in DB, or purge it
+//
+function commitDeleteRack ($rack_id = 0, $purge = FALSE)
+{
+       if ($rack_id <= 0)
+       {
+               showError ('Invalid args', __FUNCTION__);
+               die;
+       }
+       global $dbxlink;
+       if ($purge === TRUE)
+               $query = "delete from Rack where id=${rack_id} limit 1";
+       else
+               $query =
+                       "update Rack set deleted = 'yes' where id=${rack_id} 
limit 1";
+       $result = $dbxlink->query ($query);
+       if ($result == NULL)
+       {
+               showError ('SQL query failed', __FUNCTION__);
+               die;
+       }
+       return TRUE;
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Delete a row from the database (Rack Row, not a DB row...)
+//
+// Hardcoded RackRow = 3 (Chapter number for RackRow Dictionary)
+//
+function commitDeleteRow ($row_id = 0)
+{
+       $row_chapter_no = 3;    // Hardcoded chapter number (RackRow)
+
+       if ($row_id == 0)
+       {
+               showError ('Not all required args are present.', __FUNCTION__);
+               return;
+       }
+
+       global $dbxlink;
+       $query =
+               "delete from Dictionary where chapter_no=${row_chapter_no} and 
dict_key=${row_id}";
+       $result = $dbxlink->query ($query);
+       if ($result == NULL)
+       {
+               showError ('SQL query failed', __FUNCTION__);
+               die;
+       }
+       return TRUE;
+}
+
 function commitAddObject ($new_name, $new_label, $new_barcode, $new_type_id, 
$new_asset_no, $taglist = array())
 {
        global $dbxlink;
@@ -1386,7 +1476,6 @@
                "where attr_type = 'dict' group by a.attr_id, am.chapter_no, 
uint_value " .
                "order by a.attr_id, am.chapter_no, uint_value";
        $result = useSelectBlade ($query2);
-       $refcnt = array();
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
                $dict[$row['chapter_no']]['refcnt'][$row['uint_value']] = 
$row['refcnt'];
        $result->closeCursor();
diff -uNr RackTables-0.15.1/inc/functions.php RackTables/inc/functions.php
--- RackTables-0.15.1/inc/functions.php 2008-06-05 03:38:09.000000000 -0700
+++ RackTables/inc/functions.php        2008-06-08 20:14:14.000000000 -0700
@@ -153,56 +153,6 @@
        return TRUE;
 }
 
-function delRow ($row_id = 0)
-{
-       if ($row_id == 0)
-       {
-               showError ('Not all required args are present.', __FUNCTION__);
-               return;
-       }
-       if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
-       {
-               echo "Press <a 
href='?op=del_row&row_id=${row_id}&confirmed=true'>here</a> to confirm rack row 
deletion.";
-               return;
-       }
-       global $dbxlink;
-       echo 'Deleting rack row information: ';
-       $result = $dbxlink->query ("update RackRow set deleted = 'yes' where 
id=${row_id} limit 1");
-       if ($result->rowCount() != 1)
-       {
-               showError ('Marked ' . $result.rowCount() . ' rows as deleted, 
but expected 1', __FUNCTION__);
-               return;
-       }
-       echo 'OK<br>';
-       recordHistory ('RackRow', "id = ${row_id}");
-       echo "Information was deleted. You may return to <a 
href='?op=list_rows&editmode=on'>rack row list</a>.";
-}
-
-function delRack ($rack_id = 0)
-{
-       if ($rack_id == 0)
-       {
-               showError ('Not all required args are present.', __FUNCTION__);
-               return;
-       }
-       if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
-       {
-               echo "Press <a 
href='?op=del_rack&rack_id=${rack_id}&confirmed=true'>here</a> to confirm rack 
deletion.";
-               return;
-       }
-       global $dbxlink;
-       echo 'Deleting rack information: ';
-       $result = $dbxlink->query ("update Rack set deleted = 'yes' where 
id=${rack_id} limit 1");
-       if ($result->rowCount() != 1)
-       {
-               showError ('Marked ' . $result.rowCount() . ' rows as deleted, 
but expected 1', __FUNCTION__);
-               return;
-       }
-       echo 'OK<br>';
-       recordHistory ('Rack', "id = ${rack_id}");
-       echo "Information was deleted. You may return to <a 
href='?op=list_racks&editmode=on'>rack list</a>.";
-}
-
 function delObject ($object_id = 0)
 {
        if ($object_id == 0)
diff -uNr RackTables-0.15.1/inc/interface.php RackTables/inc/interface.php
--- RackTables-0.15.1/inc/interface.php 2008-06-05 03:38:09.000000000 -0700
+++ RackTables/inc/interface.php        2008-06-08 20:13:57.000000000 -0700
@@ -61,6 +61,7 @@
 {
        $tagfilter = getTagFilter();
        $tagfilter_str = getTagFilterStr ($tagfilter);
+       showMessageOrError();   // JT: 2008-06-08 Show messages / errors here 
too
        echo "<table class=objview border=0 width='100%'><tr><td class=pcleft>";
        renderTagFilterPortlet ($tagfilter, 'rack');
        echo '</td><td class=pcright>';
@@ -79,7 +80,7 @@
                echo "<td><table border=0 cellspacing=5><tr>";
                foreach ($rackList as $rack)
                {
-                       echo "<td align=center><a 
href='${root}?page=rack&rack_id=${rack['id']}'>";
+                       echo "<td align=center valign=bottom><a 
href='${root}?page=rack&rack_id=${rack['id']}'>";       // JT: 2008-06-08 
Vertical align the racks to the bottom
                        echo "<img border=0 width=${rackwidth} height=";
                        echo 3 + 3 + $rack['height'] * 2;
                        echo " title='${rack['height']} units'";
@@ -131,7 +132,7 @@
        echo "<table border=0 cellspacing=5 align='center'><tr>";
        foreach ($rackList as $rack)
        {
-               echo "<td align=center class=row_${order}><a 
href='${root}?page=rack&rack_id=${rack['id']}'>";
+               echo "<td align=center valign=bottom class=row_${order}><a 
href='${root}?page=rack&rack_id=${rack['id']}'>";    // JT: 2008-06-08 Vertical 
align the racks to the bottom
                echo "<img border=0 width=" . $rackwidth * getConfigVar 
('ROW_SCALE') . " height=";
                echo (3 + 3 + $rack['height'] * 2) * getConfigVar ('ROW_SCALE');
                echo " title='${rack['height']} units'";
@@ -529,6 +530,40 @@
        finishPortlet();
 }
 
+//
+//  Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Updated way to delete a rack, as the delRack in inc/functions.php was
+// not working correctly
+//
+function renderDeleteRackForm ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg ('rack_id');
+
+       $rack_id = $_REQUEST['rack_id'];
+
+       startPortlet ('Confirm Rack Deletion');
+       echo "<form action='${root}process.php' method=post>";
+       echo "<input type=hidden name=page value='${pageno}'>";
+       echo "<input type=hidden name=tab value='${tabno}'>";
+       echo "<input type=hidden name=op value='del'>";
+       echo "<input type=hidden name=rack_id value='${rack_id}'>";
+
+       echo "<table border=0 cellspacing=0 cellpadding=3 width='100%'>";
+       echo "<tr><td>";
+       echo "Deleting a rack marks it as deleted in the database.<br>";
+       echo "If you want it removed completely, check the Purge box.<br><br>";
+       echo "Are you sure you want to delete this rack?";
+       echo "</td></tr>";
+       echo "<tr><td><input type=checkbox name=purge value=0>Purge from 
database<br>&nbsp;</td></tr>";
+       echo "<tr><td><input type=submit value='Delete this Rack'></td></tr>";
+       echo "</table>";
+       echo "</form>";
+       finishPortlet();
+}
+
+
 // This is a helper for creators and editors.
 function printSelect ($rowList, $select_name, $selected_id = 1)
 {
@@ -3497,6 +3532,7 @@
                        "src='${root}${img['path']}' " .
                        "border=0 " .
                        ($tabindex ? '' : "tabindex=${tabindex}") .
+                       (empty ($title) ? '' : " title='${title}'") .   // JT: 
Add title to input hrefs too
                        ">";
        else
                echo
@@ -4997,4 +5033,252 @@
        echo '</pre>';
 }
 
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Form to allow creating multiple rows from the Rackspace page
+//
+function renderNewRowForm ()
+{
+       global $root, $pageno, $tabno;
+
+       echo "<table border=0 width='100%'><tr><td valign=top>";
+       // Render a form for the next.
+       startPortlet ('Add Rows');
+       echo "<form action='${root}process.php' method=post>";
+       echo "<input type=hidden name=page value=${pageno}>";
+       echo "<input type=hidden name=tab value=${tabno}>";
+       echo "<input type=hidden name=op value='add'>";
+
+       echo '<table border=0 align=center>';
+       echo "<tr><th class=center>Enter row names, one per line:</th></tr>\n";
+        echo "<tr><td class=center><textarea name=row_names cols=40 
rows=15></textarea></td></tr>\n";
+       echo "<tr><td class=submit><input type=submit name=got_mdata 
value='Add'></td></tr>\n";
+       echo '</form></table>';
+       finishPortlet();
+       echo '</td></tr>';
+       echo '</table>';
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Form to delete an empty row
+//
+function renderDeleteRowForm ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg ('row_id');
+
+       $row_id = $_REQUEST['row_id'];
+
+       startPortlet ('Confirm Row Deletion');
+       echo "<form action='${root}process.php' method=post>";
+       echo "<input type=hidden name=page value='${pageno}'>";
+       echo "<input type=hidden name=tab value='${tabno}'>";
+       echo "<input type=hidden name=op value='del'>";
+       echo "<input type=hidden name=row_id value='${row_id}'>";
+
+       echo "<table border=0 cellspacing=0 cellpadding=3 width='100%'>";
+       echo "<tr><td>";
+       echo "You are about to delete this row from the database.<br>";
+       echo "This action can NOT be undone!<br>";
+       echo "Are you sure you want to delete this row?";
+       echo "</td></tr>";
+       echo "<tr><td><input type=submit value='Delete this Row'></td></tr>";
+       echo "</table>";
+       echo "</form>";
+       finishPortlet();
+
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Create a form for managing HW Type dictionary objects
+// I Call these Hardware Categories, but basically "HW Type" Dictionary 
Attribute editing
+//
+function renderHWObjCategories ()
+{
+       global $root, $pageno, $tabno;
+       $attrMap = getObjectCategories(2);      // HW Type
+       showMessageOrError();
+       startPortlet ('Hardware Categories');
+       echo "<table cellspacing=0 cellpadding=5 align=center 
class=widetable>\n";
+       echo '<tr align=left><th>&nbsp;</th><th>Category</th><th>Item 
Count</th><th>&nbsp;</th></tr>';
+
+       foreach ($attrMap as $attr)
+       {
+               echo '<tr><td>';
+               if ($attr['objcount'])
+                       printImageHREF ('nodelete', 'contains ' . 
$attr['objcount'] . ' item(s)');
+               else
+               {
+                       echo "<form action='${root}process.php' method=post>";
+                       echo "<input type=hidden name=page value='${pageno}'>";
+                       echo "<input type=hidden name=tab value='${tabno}'>";
+                       echo "<input type=hidden name=op value=del>";
+                       echo "<input type=hidden name=attr_id value='2'>";      
// HW Type
+                       echo "<input type=hidden name=objtype_id 
value='${attr['id']}'>";
+                       echo "<input type=hidden name=chapter_no 
value='${attr['chapter_no']}'>";
+                       printImageHREF ('delete', '', TRUE);
+                       echo '</form>';
+               }
+               echo '</td>';
+               echo "<form action='${root}process.php' method=post>";
+               echo "<input type=hidden name=page value='${pageno}'>";
+               echo "<input type=hidden name=tab value='${tabno}'>";
+               echo "<input type=hidden name=op value=upd>";
+               echo "<input type=hidden name=dict_key value='${attr['id']}'>";
+               echo "<input type=hidden name=chapter_no 
value='${attr['chapter_no']}'>";
+               echo "<td align=left>";
+               echo "<input type=text name=dict_value size=20 
value='${attr['name']}'>";
+               echo "</td>";
+               echo "<td><a 
href='${root}?page=hwobjs&tab=items&objtype_id=${attr['chapter_no']}' 
title='Edit Items...'>${attr['objcount']}</a></td>";
+               echo "<td>";
+               printImageHREF ('save', 'Save category', TRUE);
+               echo "</form>";
+               echo "</td></tr>\n";
+       }
+       echo "<form action='${root}process.php' method=post>";
+       echo "<input type=hidden name=page value='${pageno}'>";
+       echo "<input type=hidden name=tab value='${tabno}'>";
+       echo "<input type=hidden name=op value=add>";
+       echo "<input type=hidden name=attr_id value='2'>";      // HW Type
+       echo '<tr><td>';
+       printImageHREF ('add', '', TRUE);
+       echo '<td align=left colspan=3>';
+       echo "<input type=text size=20 name=objtype_name>";
+       echo '</tr>';
+       echo '</form>';
+       echo "</table>\n";
+       finishPortlet();
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Display a page to edit Hardware items by category.
+//
+// attr_id hardcoded... to "HW Type" (2)
+//
+function renderHWObjItems ()
+{
+       global $root, $pageno, $tabno;
+       $attr_id = 2;   // HW Type
+
+       if (isset($_REQUEST['objtype_id']))
+               $objtype_id = $_REQUEST['objtype_id'];
+       else
+               $objtype_id = 0;
+
+       $dict = getDict();              // Get all Items
+       $categories = getObjectCategories($attr_id);
+       showMessageOrError();
+       startPortlet ('Select Category');
+
+       // Find all categories we want allow the user to choose
+       $sel = array();
+       foreach ($categories as $id => $info)
+               $sel[$info['chapter_no']] = $info['name'];
+
+       // Form to choose the category of items to edit
+       echo "<table cellspacing=0 cellpadding=5 align=center>\n";
+       echo "<form action='${root}' method=get>";
+       echo "<input type=hidden name=page value='${pageno}'>";
+       echo "<input type=hidden name=tab value='${tabno}'>";
+       echo '<tr><td valign=top>';
+       printSelect ($sel, 'objtype_id', $objtype_id);
+       echo "</td><td>";
+       echo "<input type=submit value=Select>";        
+       echo "</form>";
+       echo '</td></tr></table>';
+       finishPortlet();
+
+       // Find the selected chapter
+       if (isset($dict[$objtype_id]))
+       {
+               $chapter = $dict[$objtype_id];
+               startPortlet('Category Items');
+               echo "<table cellspacing=0 cellpadding=5 align=center 
class=widetable>\n";
+               echo 
"<tr><th>&nbsp;</th><th>Manufacturer</th><th>Model</th><th>URL</th><th>&nbsp;</th></tr>";
+
+               // Form to Add new item
+               echo "<form action='${root}process.php' method=post>";
+               echo "<input type=hidden name=page value='${pageno}'>";
+               echo "<input type=hidden name=tab value='${tabno}'>";
+               echo "<input type=hidden name=op value='add'>";
+               echo "<input type=hidden name=chapter_no value='$objtype_id'>";
+               echo "<tr>";
+               echo "<td>&nbsp;</td>";
+               echo "<td><input type=text name=man size=20></td>";
+               echo "<td><input type=text name=mod size=20></td>";
+               echo "<td><input type=text name=url size=30></td>";
+               echo "<td>";
+               printImageHREF ('add', 'Add new', TRUE);
+               echo "</td>";
+               echo "</tr>";
+               echo "</form>";
+
+               // this needs to lookup the items for that category
+               foreach ($chapter['word'] as $key => $value)
+               {
+                       // Split the Description / URL (from parseWikiLink)
+                       if (preg_match ('/^\[\[.+\]\]$/', $value))
+                       {
+                               $value = preg_replace ('/^\[\[(.+)\]\]$/', 
'$1', $value);
+                               $s = explode ('|', $value);
+                               $desc = trim ($s[0]);
+                               $url = trim ($s[1]);
+                       }
+                       else
+                       {
+                               $desc = $value;
+                               $url  = "";
+                       }
+
+                       if (preg_match('%GPASS%', $desc))
+                       {
+                               $s = explode ('%GPASS%', $desc, 2);
+                               $manufacturer = $s[0];
+                               $model = $s[1];
+                       }
+                       else
+                       {
+                               $manufacturer = $desc;
+                               $model = "";
+                       }                       
+                       // Print it out
+                       echo "<form action='${root}process.php' method=post>";
+                       echo "<input type=hidden name=page value='${pageno}'>";
+                       echo "<input type=hidden name=tab value='${tabno}'>";
+                       echo "<input type=hidden name=op value='upd'>";
+                       echo "<input type=hidden name=objtype_id 
value='$chapter'>";
+                       echo "<input type=hidden name=chapter_no 
value='${chapter['no']}'>";
+                       echo "<input type=hidden name=dict_key value='${key}'>";
+                       echo "<tr><td>";
+                       // Prevent deleting words currently used somewhere.
+                       if ($chapter['refcnt'][$key])
+                               printImageHREF ('nodelete', 'referenced ' . 
$chapter['refcnt'][$key] . ' time(s)');
+                       else
+                       {
+                               echo "<a 
href='${root}process.php?page=${pageno}&tab=${tabno}&op=del&chapter_no=${chapter['no']}&dict_key=${key}'>";
+                               printImageHREF ('delete', 'Delete word');
+                               echo "</a>";
+                       }
+                       echo '</td>';
+                       echo "<td><input type=text name=man size=20 
value='${manufacturer}'></td>";
+                       echo "<td><input type=text name=mod size=20 
value='${model}'></td>";
+                       echo "<td class=tdright><input type=text name=url 
size=30 value='${url}'></td>";
+                       echo "<td>";
+                       printImageHREF ('save', 'Save item', TRUE);
+                       echo "</td>";
+                       echo "</tr></form>\n";
+               }
+               echo "</table></td>";
+               finishPortlet();
+       }
+}
+
+
 ?>
diff -uNr RackTables-0.15.1/inc/navigation.php RackTables/inc/navigation.php
--- RackTables-0.15.1/inc/navigation.php        2008-06-03 08:08:08.000000000 
-0700
+++ RackTables/inc/navigation.php       2008-06-08 20:21:40.000000000 -0700
@@ -18,12 +18,15 @@
 $page['rackspace']['parent'] = 'index';
 $tab['rackspace']['default'] = 'Browse';
 $tab['rackspace']['history'] = 'History';
+$tab['rackspace']['addmore'] = 'Add more';                     // JT: 
2008-06-08 Add rows
 $tab['rackspace']['firstrow'] = 'Click me!';
 $trigger['rackspace']['firstrow'] = 'trigger_emptyRackspace';
 $tabhandler['rackspace']['default'] = 'renderRackspace';
 $tabhandler['rackspace']['history'] = 'renderRackspaceHistory';
+$tabhandler['rackspace']['addmore'] = 'renderNewRowForm';      // JT: 
2008-06-08 Add rows
 $tabhandler['rackspace']['firstrow'] = 'renderFirstRowForm';
 $tabextraclass['rackspace']['firstrow'] = 'attn';
+$ophandler['rackspace']['addmore']['add'] = 'addRow';          // JT: 
2008-06-08 Add Rows
 
 $page['objects']['title'] = 'Objects';
 $page['objects']['parent'] = 'index';
@@ -39,9 +42,13 @@
 $tab['row']['default'] = 'View';
 $tab['row']['newrack'] = 'Add new rack';
 $tab['row']['tagroller'] = '[Tag roller]';
+$tab['row']['del'] = 'Delete';                         // JT: 2008-06-08 
Delete empty row
 $tabhandler['row']['default'] = 'renderRow';
 $tabhandler['row']['newrack'] = 'renderNewRackForm';
 $tabhandler['row']['tagroller'] = 'renderTagRollerForRow';
+$tabhandler['row']['del'] = 'renderDeleteRowForm';     // JT: 2008-06-08 
Delete empty row
+$trigger['row']['del'] = 'trigger_delRowForm';         // JT: 2008-06-08 
Delete empty row
+$ophandler['row']['del']['del'] = 'delRow';            // JT: 2008-06-08 
Delete empty row
 
 $page['rack']['title_handler'] = 'dynamic_title_rack';
 $page['rack']['bypass'] = 'rack_id';
@@ -54,13 +61,17 @@
 $tab['rack']['design'] = 'Design';
 $tab['rack']['problems'] = 'Problems';
 $tab['rack']['tags'] = 'Tags';
+$tab['rack']['del'] = 'Delete';                                // JT: 
2008-06-08 Easily delete rack tab
 $tabhandler['rack']['default'] = 'renderRackPage';
 $tabhandler['rack']['edit'] = 'renderEditRackForm';
 $tabhandler['rack']['design'] = 'renderRackDesign';
 $tabhandler['rack']['problems'] = 'renderRackProblems';
 $tabhandler['rack']['tags'] = 'renderRackTags';
+$tabhandler['rack']['del'] = 'renderDeleteRackForm';   // JT: 2008-06-08 
Easily delete rack handler
 $trigger['rack']['tags'] = 'trigger_tags';
+$trigger['rack']['del'] = 'trigger_delRackForm';       // JT: 2008-06-08 
Easily delete rack trigger
 $ophandler['rack']['tags']['save'] = 'saveRackTags';
+$ophandler['rack']['del']['del'] = 'delRack';          // JT: 2008-06-08 
Easily delete rack handler
 
 $page['objgroup']['title_handler'] = 'dynamic_title_objgroup';
 $page['objgroup']['handler'] = 'renderObjectGroup';
@@ -336,6 +347,25 @@
 $ophandler['tagtree']['edit']['createTag'] = 'createTag';
 $ophandler['tagtree']['edit']['updateTag'] = 'updateTag';
 
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// the hwobjs page allows for easier editing of HW Type Dictionary Attributes
+// without having to go through the manual process of adding/udpdating/deleting
+// each required peice
+$page['hwobjs']['title'] = 'Hardware Objects';
+$page['hwobjs']['parent'] = 'config';
+$tab['hwobjs']['default'] = 'Categories';
+$tab['hwobjs']['items'] = 'Items';
+$tabhandler['hwobjs']['default'] = 'renderHWObjCategories';
+$tabhandler['hwobjs']['items'] = 'renderHWObjItems';
+$ophandler['hwobjs']['default']['add'] = 'supplementObjectCategories';
+$ophandler['hwobjs']['default']['del'] = 'reduceObjectCategories';
+$ophandler['hwobjs']['default']['upd'] = 'updateObjectCategories';
+$ophandler['hwobjs']['items']['add'] = 'supplementObjectItems';
+$ophandler['hwobjs']['items']['del'] = 'reduceObjectItems';
+$ophandler['hwobjs']['items']['upd'] = 'updateObjectItems';
+
+
 $page['reports']['title'] = 'Reports';
 $page['reports']['parent'] = 'index';
 $page['reports']['handler'] = 'renderReportSummary';
diff -uNr RackTables-0.15.1/inc/ophandlers.php RackTables/inc/ophandlers.php
--- RackTables-0.15.1/inc/ophandlers.php        2008-06-03 08:08:08.000000000 
-0700
+++ RackTables/inc/ophandlers.php       2008-06-08 20:13:43.000000000 -0700
@@ -1359,4 +1359,283 @@
                return "${root}?page=${pageno}&tab=${tabno}&error=" . urlencode 
("Could not update tag '${tagname}' because of error '${ret}'");
 }
 
+//
+//  Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Function to delete a rack, purge from database if requested
+//
+function delRack ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg('rack_id');       // Rack to delete
+       $rack_id = $_REQUEST['rack_id'];
+       $purge   = FALSE;
+
+       // Get my Row ID so we can return to that row
+       $rack = getRackData($rack_id, TRUE);
+       $row_id = $rack['row_id'];
+
+       // Attempt delete, if successful, return to row, if failed, return to 
my rack
+       if (isset($_REQUEST['purge']))
+               $purge = TRUE;
+
+       if (commitDeleteRack ($rack_id, $purge) === TRUE)
+       {
+               recordHistory ('Rack', "id = ${rack_id}");
+               return "${root}?page=row&row_id=${row_id}&message=" . urlencode 
('Rack deleted.');
+       }
+       else
+               return "${root}?page=${pageno}&rack_id=${rack_id}&error=" . 
urlencode ("Deletion failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Add rows, supports multiple at one time
+//
+function addRow()
+{
+       global $root, $pageno, $tabno;
+       assertStringArg ('row_names', __FUNCTION__, TRUE);
+       $message = "";
+       $error = "";
+
+       // copy-and-paste from renderAddMultipleObjectsForm()
+       $names1 = explode ('\n', $_REQUEST['row_names']);
+       $names2 = array();
+       foreach ($names1 as $line)
+       {
+               $parts = explode ('\r', $line);
+               reset ($parts);
+               if (empty ($parts[0]))
+                       continue;
+               else
+                       $names2[] = rtrim ($parts[0]);
+       }
+       foreach ($names2 as $cname)
+               if (commitSupplementDictionary ( 3, $cname) === TRUE)
+                       $message .= " '${cname}'";
+               else
+                       $error .= " '${cname}'";
+
+       return "${root}?page=rackspace" .
+               (empty($message) ? "" : "&message=" . urlencode ("Added row(s): 
" . $message)) .
+               (empty($error) ? "" : "&error=" . urlencode ("Failed to add 
row(s): " . $error));
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Delete the row from the Dictionary
+//
+function delRow ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg('row_id', __FUNCTION__);
+       $row_id = $_REQUEST['row_id'];
+
+       if (commitDeleteRow ($row_id) === TRUE)
+       {
+               recordHistory ('RackRow', "id = ${row_id}");
+               return "${root}?page=rackspace&message=" . urlencode ('Row 
deleted.');
+       }
+       else
+               return "${root}?page=${pageno}&row_id=${row_id}&error=" . 
urlencode ("Deletion failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Changes names in Dictionary and Chapter
+// Copied from updateDictionary
+//
+// RackObjectType hardcoded...
+//
+function updateObjectCategories ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg ('chapter_no', __FUNCTION__);
+       assertUIntArg ('dict_key', __FUNCTION__);
+       assertStringArg ('dict_value', __FUNCTION__);
+       $rack_obj_chapter = 1;  // RackObjectType
+       $chapter_name = trim($_REQUEST['dict_value'] . " Objects");
+
+       if (commitUpdateDictionary ($rack_obj_chapter, $_REQUEST['dict_key'], 
$_REQUEST['dict_value']) === TRUE)
+               if (commitUpdateChapter ($_REQUEST['chapter_no'], 
$chapter_name) === TRUE)
+                       return "${root}?page=${pageno}&tab=${tabno}&message=" . 
urlencode ('Update succeeded.');
+               else
+                       return "${root}?page=${pageno}&tab=${tabno}&error=" . 
urlencode ("Update failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// This puts all of the steps needed to create a new object category:
+//  - Create Chapter, Add to RackObjectType Chapter, Add to AttribureMap 
(based on attr_id) 
+//
+// Has RackObjectType hardcoded...
+// Initially tested only with HW Type (attr_id = 2) but should work for any 
Dictionary Attribute
+//
+function supplementObjectCategories ()
+{
+       global $root, $pageno, $tabno;
+       $status = FALSE;        // Set this on success
+       assertStringArg ('objtype_name', __FUNCTION__);
+       assertUIntArg ('attr_id', __FUNCTION__);
+       $rack_obj_chapter = 1;  // RackObjectType
+       $attr_id = $_REQUEST['attr_id'];
+       $objtype_name = trim($_REQUEST['objtype_name']);
+       $chapter_name = "$objtype_name Objects";
+       $chapter_no   = 0;
+       $dict_key     = 0;
+
+       if (commitAddChapter ($chapter_name) === TRUE)
+       {
+               // Find the inserted chapter number
+               $dict = getDict();
+               foreach ($dict as $chapter)
+                       if (strcmp($chapter['name'], $chapter_name) === 0)
+                       {
+                               $chapter_no = $chapter['no'];
+                               break;
+                       }
+
+               if ($chapter_no != 0)
+               {
+                       if (commitSupplementDictionary ($rack_obj_chapter, 
$objtype_name) === TRUE)
+                       {
+                               $objtypes = getObjectTypeList();
+                               foreach ($objtypes as $id => $name)
+                                       if (strcmp($name, $objtype_name) === 0)
+                                       {
+                                               $objtype_id = $id;
+                                               break;
+                                       }
+                               
+                               if ($objtype_id != 0)
+                                       if (commitSupplementAttrMap ($attr_id, 
$objtype_id, $chapter_no) === TRUE)
+                                               $status = TRUE;
+                       }
+               }
+       }
+
+       // Return messages
+       if ($status)
+               return "${root}?page=${pageno}&tab=${tabno}&message=" . 
urlencode ('Supplement succeeded.');
+       else
+               return "${root}?page=${pageno}&tab=${tabno}&error=" . urlencode 
("Supplement failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// This puts all of the steps needed to remove a new object category:
+//  - Remove from AttributeMap, Remove from Chapter RackObjectType, Remove 
Chapter
+//
+// RackObjectType is hardcoded...
+// tested only with HW Type (attr_id = 2) but should work with any Dictionary 
Attribute type
+//
+function reduceObjectCategories ()
+{
+       global $root, $pageno, $tabno;
+       $status = FALSE;        // Set this on success
+       assertUIntArg ('objtype_id', __FUNCTION__);
+       assertUIntArg ('chapter_no', __FUNCTION__);
+       assertUIntArg ('attr_id', __FUNCTION__);
+       $rack_obj_chapter = 1;  // RackObjectType
+
+
+       if (commitReduceAttrMap ($_REQUEST['attr_id'], $_REQUEST['objtype_id']) 
=== TRUE)
+               if (commitReduceDictionary ($rack_obj_chapter, 
$_REQUEST['objtype_id']) === TRUE)
+                       if (commitDeleteChapter ($_REQUEST['chapter_no']))
+                               return 
"${root}?page=${pageno}&tab=${tabno}&message=" . urlencode ('Reduce 
succeeded.');
+                       else
+                               return 
"${root}?page=${pageno}&tab=${tabno}&error=" . urlencode ("Reduce failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Add objects to the Dictionary for a Dictionary type Attribute
+// This handles puting the Manufacturer, model, and url into the Wiki format
+// used to store the data.
+// Leaves the chapter number in the url, needed for my easy item editor
+//
+function supplementObjectItems ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg('chapter_no', __FUNCTION__);
+       assertStringArg ('man', __FUNCTION__);
+       assertStringArg ('mod', __FUNCTION__, TRUE);
+       assertStringArg ('url', __FUNCTION__, TRUE);
+       $desc = trim($_REQUEST['man']);
+
+       // Rebuild the Description
+       if (!empty($_REQUEST['mod']))
+               $desc .= "%GPASS%" . trim($_REQUEST['mod']);
+
+       // Put description and url back together, if there is a url
+       if (!empty($_REQUEST['url']))
+               $dict_value = "[[ " . $desc . " | " . trim($_REQUEST['url']) . 
" ]]";
+       else
+               $dict_value = $desc;
+
+       if (commitSupplementDictionary ($_REQUEST['chapter_no'], $dict_value) 
=== TRUE)
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&message="
 . urlencode ('Add succeeded.');
+       else
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&error="
 . urlencode ("Add failed!");
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Remove objects from Dictionary
+// Leaves the chapter number in the url, needed for my easy item editor
+//
+function reduceObjectItems ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg ('chapter_no', __FUNCTION__);
+       assertUIntArg ('dict_key', __FUNCTION__);
+
+       if (commitReduceDictionary ($_REQUEST['chapter_no'], 
$_REQUEST['dict_key']))
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&message="
 . urlencode ('Reduce succeeded.');
+       else
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&error="
 . urlencode ("Reduce failed!");
+}
+
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// Update objects in Dictionary
+//  - Handles adding %GPASS% when sorting by Manufacturer
+//  - Handled Wiki encoding Manufacturer, Model, and URL
+//
+function updateObjectItems ()
+{
+       global $root, $pageno, $tabno;
+       assertUIntArg ('chapter_no', __FUNCTION__);
+       assertUIntArg ('dict_key', __FUNCTION__);
+       assertStringArg ('man', __FUNCTION__);
+       assertStringArg ('mod', __FUNCTION__, TRUE);
+       assertStringArg ('url', __FUNCTION__, TRUE);
+       $desc = trim($_REQUEST['man']);
+
+       // Rebuild the Description
+       if (!empty($_REQUEST['mod']))
+               $desc .= "%GPASS%" . trim($_REQUEST['mod']);
+
+       // Put these back together (Wiki encode)
+       if (!empty($_REQUEST['url']))
+               $dict_value = "[[ " . $desc . " | " . trim($_REQUEST['url']) . 
" ]]";
+       else
+               $dict_value = $desc;
+
+       if (commitUpdateDictionary ($_REQUEST['chapter_no'], 
$_REQUEST['dict_key'], $dict_value) === TRUE)
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&message="
 . urlencode ('Update succeeded.');
+       else
+               return 
"${root}?page=${pageno}&tab=${tabno}&objtype_id=${_REQUEST['chapter_no']}&error="
 . urlencode ("Update failed!");
+}
+
 ?>
diff -uNr RackTables-0.15.1/inc/triggers.php RackTables/inc/triggers.php
--- RackTables-0.15.1/inc/triggers.php  2008-03-26 12:38:44.000000000 -0700
+++ RackTables/inc/triggers.php 2008-06-08 20:14:10.000000000 -0700
@@ -102,4 +102,32 @@
        return count ($taglist) > 0;
 }
 
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// If the rack is empty, display the delete tab
+//
+function trigger_delRackForm ()
+{
+       assertUIntArg ('rack_id', __FUNCTION__);
+       $rackData = getRackData ($_REQUEST['rack_id'], TRUE);
+       if (getRSUforRack($rackData) === 0)
+               return TRUE;
+       return FALSE;
+}
+
+//
+//   Author: Jonathan Thurman
+// Modified: 2008-06-08
+// If the row is empty, display the delete tab
+//
+function trigger_delRowForm ()
+{
+       assertUIntArg('row_id', __FUNCTION__);
+       $rowData = getRacksForRow($_REQUEST['row_id']);
+       if (empty($rowData))
+               return TRUE;
+       return FALSE;
+}
+
 ?>

Other related posts: