Listing menu items
This is the most complex listing we have written so far. But don't panic. We use the the flexible DataBrowser class to create a listing. In the browser we will have the following extras, all of which are not found in the base MenuItems table:
- Links to the pages for testing
- Link type
- Page name
- Activity icon, clickable to toggle item on or off
- Move up and move down icons
CREATE TABLE linktypes ( id int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255), PRIMARY KEY (id)); INSERT INTO linktypes (id, name) VALUES(1, 'Internal page'),(2, 'Internal link'),(3, 'External link'),(4, 'Parent title'),(5, 'Subtitle'),(6, 'Separator');SELECT simple_menuitems.id, idx, menutext, url, linktypes.name, cms_pages.title, concat(simple_menuitems.id,'|',active), parent_id, simple_menuitems.id, simple_menuitems.id FROM simple_menuitems, cms_pages, linktypes WHERE menuid=<menuid> linktypes.id=linktype cms_pages.id=pageid ORDER BY idxparentid is not used yet and menuid can be left outside as well as we have but one menu. We will write a more flexible two-level editor a bit later.<?php require_once "lib/DataBrowser.class.php"; class MenuEditor_List_module extends CmsModule { // use a DataBrowser class variable this time var $db = null; // change the table name here or in the var $items_table = "simple_menuitems"; var $menus_table = "menus"; var $indexes = array(); var $lowestidx = 99999; var $highestidx = -9999; // activity icons - change if needed var $icon_active = "icon/oksmall.gif"; var $icon_inactive = "icon/disabledsmall.png"; // default siteroot to the upper directory var $siteroot = "../"; var $menucount = 0; var $menuid = 1; /* * init() initializes quite a few class variables as we have * quite a bit of functionality in this class */ function init(){ parent::init(); // check the "table" parameter if ($this->vars['items_table'] != "") $this->items_table = $this->vars['items_table']; if ($this->vars['siteroot'] != "") $this->siteroot = $this->vars['siteroot']; if ($_GET['menuid'] != "") $this->menuid = $_GET['menuid']; else $menuid = getSQLvalue("SELECT MIN(menuid) FROM $this->items_table"); $this->db = new DataBrowser($this); // hide the "arrow down display link $this->db->setShowDisplayLink(false); // load id->idx value pairs from the table $query = "SELECT $this->items_table.id, idx " . "FROM $this->items_table " . "WHERE menuid=$this->menuid "; $result = @mysql_query($query); if (mysql_error()) die(mysql_error()); // create the array and find hi/lo values for idx while ($obj = mysql_fetch_object($result)){ $this->indexes[$obj->id] = $obj->idx; if ($this->lowestidx > $obj->idx) $this->lowestidx = $obj->idx; if ($this->highestidx < $obj->idx) $this->highestidx = $obj->idx; } $this->menucount = getSQLvalue("SELECT COUNT(*) FROM menus"); } // return an active/inactive image inside a toggle href function cb_active($paramPair){ $parts = explode("|",$paramPair); $id = $parts[0]; $value = $parts[1]; $url = "?act=toggle_menuitem&id=$id"; if ($value) $img = "icon/oksmall.png"; else $img = "icon/disabledsmall.png"; return "<a href='$url' alt='toggle' >" . "<img src='$img' border='0' /></a>"; } //shorten thr url if too long function cb_url($url,$obj){ $displayUrl = $url; if (strlen($url)>20) $displayUrl = substr($url,0,18)."..."; else $displayUrl = $url; return "<a href='".$obj->siteroot.$url."' target='_blank' >" . "$displayUrl</a>"; } function cb_moveup($id,$obj){ //if smallest index do not show arrow up if ($obj->indexes[$id] == $obj->lowestidx) return ""; return "<a href='?act=move_menuitem&dir=up&id=$id' >" . "<img src='icon/move_up.png' border='0' /></a>"; } function cb_movedown($id,$obj){ // if highest index do not show arrow down if ($obj->indexes[$id] == $obj->highestidx) return ""; return "<a href='?act=move_menuitem&dir=dn&id=$id' >" . "<img src='icon/move_down.png' border='0' /></a>"; } // function fetch(){ $this->db->setSmarty(new Smarty()); $EDIT_FORM = "edit_menuitem"; // dummy switch structure for the future switch ($this->GET['more']){ default: $this->db->settitle(""); } // create query: this must match the column addition further down $this->db->setQuery("SELECT $this->items_table.id,$this->items_table.idx, " . "menutext,$this->items_table.url, " . "linktypes.name,cms_pages.title," . "concat($this->items_table.id,'|',active),parent_id," . "$this->items_table.id,$this->items_table.id " . "FROM $this->items_table,cms_pages,linktypes " . "WHERE menuid = $this->menuid ". "AND linktypes.id=linktype " . "AND cms_pages.id=pageid " . "ORDER BY idx"); // create links for editing/deleting/inserting $this->db->setEditLinkUrl("?act=$EDIT_FORM&editact=edit"); $this->db->setDeleteLinkUrl("?act=$EDIT_FORM&editact=delete"); $this->db->setInsertLinkUrl("?act=$EDIT_FORM&editact=insert"); $this->db->setInsertButtonText("Add Item"); // add coluns to grid $this->db->addColumn("id",10,"text",0); $this->db->addColumn("idx",10,"text",0); $this->db->addColumn("MenuText",160,"text",1); $this->db->addColumn("URL",100,"text",1,'cb_url',$this); $this->db->addColumn("Type",80,"text",1); $this->db->addColumn("Page",150,"text",1); $this->db->addColumn("Act",20,"text",1,'cb_active'); $this->db->addColumn("Parent",50,"text",0); $this->db->addColumn("Up",20,"text",1,'cb_moveup',$this); $this->db->addColumn("Dn",20,"text",1,'cb_movedown',$this); $this->db->addFunctionButton("?act=addMenu","Add new menu"); // add hidden fields needed for links $this->db->addHiddenFields("act"); $this->db->addHiddenField("menuid",$this->menuid); // get data and find low/high values for "idx" $this->db->getData(); if ($this->menucount > 1){ $this->db->addSelectionForm("menuid","Menu: ", "SELECT id,name FROM " . "$this->menus_table", $this->menuid,$PHP_SELF); } return $this->db->fetchTemplate("DataBrowser.tpl"); } } ?>
indexes array will simply contain the item id -> idx value pair. We need this when trying to move a menu item up or down. Variables lowestidx and highestidx contain the limits and are needed for the same purpose as the indexes array.siteroot variable is for fun only: we can open the page from the menu editor if we know the relative path. This could be a lot nicer, of course, but will do.cb_active callback returns a clickable image link that launches a link to our admin page with parameters act=toggle_menuitem&id=5, for example. Needless to say we need to write a small module to toggle the activity state. This is an easy way of doing it now that we have not yet been diving in the Ajaxian Ocean.cb_url callback should be self-explanatory now.cb_moveup and cb_movedown work much the same way the cb_active does. If the current menu item is the top one it cb_moveup returns an empty string and if it is the bottom one cb_movedown returns an empty string. In all other cases they return links that – like cb_active – direct the browser to the respective module. And we must, of course, write this tiny module as well.
Update form
<?php require_once "lib/UpdateForm.class.php"; class MenuEditor_Edit_module extends CmsModule { var $table = "simple_menuitems"; public function init(){ parent::init(); } public function fetch(){ // save the referer for use in the DBUpdate redirect $_SESSION['_ref'] = $_SERVER['HTTP_REFERER']; $form = new UpdateForm(); setlocale(LC_ALL,$GLOBALS['cms_config']['locale']); // get stuff needed from $_GET $editact = $_GET['editact']; $id = $_GET['id']; // Initialize title switch($editact){ case "edit" : $form->setTitle("Edit this item"); $form->setAction("./index.php?act=save_menuitem&saveact=update"); $form->setSubmitText("Save item"); break; case "delete": $form->setTitle("Remove this item"); $form->setAction("./index.php?act=save_menuitem&saveact=delete"); $form->setSubmitText("Delete item"); // do not allow for editing if we are deleting $form->setReadOnly(true); break; case "insert": $form->setTitle("Add new item"); $form->setAction("./index.php?act=save_menuitem&saveact=insert"); $form->setSubmitText("Save new item"); break; default: $form->setTitle("Unknown editact: $editact"); } // if edit/delete show the form with data if (($editact=="edit") || ($editact=="delete")) { $query = "select id," . "idx,menutext,url,linktype,pageid,active,parent_id ". "FROM $this->table where id=$id"; $result = mysql_query($query) or die ("[".$query."], ".mysql_error()); if (mysql_num_rows($result)==0) { return "<h1>Alert: No data found at record: $id"; } $row = mysql_fetch_object($result); $menutext = $row->menutext; $url = $row->url; $linktype = $row->linktype; $pageid = $row->pageid; $active = $row->active; $parentid = $row->parent_id; // create an empty form to insert data } else if ($editact=='insert'){ // default to today's date $idx = $form->getSQLvalue("SELECT MAX(idx) FROM $this->table"); } // Add the fields and possible subtitles $form->addField("menutext",$menutext,"Menu text","text"); $form->limitInputLength(-1,30); $form->addField("url",$url,"Link URL","text"); $form->addField("linktype",$linktype,"Link type","select"); $form->addOptionSQL(-1,"SELECT id,name FROM linktypes"); $form->addField("pageid",$pageid,"Dynamic page","select"); $form->addOptionsSQL(-1,"SELECT id,title FROM cms_pages order by id"); //$form->addField("parentid",$parentif,"select","Parent item"); $form->addField("active",$active,"Active","checkbox"); $form->addHiddenField("id",$id); $form->addHiddenField("_ref",$_SERVER['HTTP_REFERER']); $form->setSmarty(new Smarty()); $form->assignSmarty(); $output = $form->fetchTemplate("UpdateForm.tpl"); return $output; } } ?>
This should be rather straightforward, there is nothing special here. The result can be seen below. There is, however, a glimpse of future in the form of 'parent selection':

Database update module
Now we can write the menuitem saving code which has just one small thing we have to look at, well, it is documented so you will catch it, anyhow
<?php require_once "lib/DBUpdate.class.php"; class MenuEditor_Saveitem_module extends CmsModule { var $table = "simple_menuitems"; function init(){ parent::init(); } function fetch(){ $id = $_POST['id']; $menutext = $_POST['menutext']; $url = $_POST['url']; $linktype = $_POST['linktype']; $pageid = $_POST['pageid']; $active = $_POST['active']; $saveact = $_POST['saveact']; $so = new DBUpdate(); // find a new value for idx using a utils library routine if ($saveact=='insert'){ $maxidx = getSQLvalue("SELECT MAX(idx) FROM $this->table"); $idx = $maxidx+1; $so->addField("idx",$idx,"varchar"); } // create the url here if dynamic page if ($linktype==1){ $url = "?pageid=$pageid"; } $so->addField('menutext',$menutext,'varchar'); $so->addField('url',$url,'text'); $so->addField('linktype',$linktype,'text'); $so->addField('pageid',$pageid,'text'); $so->addField("active",$active,"checkbox"); $so->setTableName($this->table); $so->setSaveAct($saveact); $so->setCondition("id = '$id'"); $so->createQuery(); $result = $so->commitQuery(); if (!$result) die ($so->msg_error); // retrieve the saved referer value $url = $_POST['_ref']; header("location: $url"); exit; } } ?>
Toggle and move
Finally let's write the toggle and move menuitem modules. The toggle code is pretty simple as we are using the standard module technology which does the dirty initial task for us:
<?php class MenuEditor_Toggleitem_module extends CmsModule { var $table = "simple_menuitems"; function init(){ parent::init(); if ($this->vars['table'] != ""){ $this->table = $this->vars['table']; } } function fetch(){ $id = $_GET['id']; $ref = $_SERVER['HTTP_REFERER']; $alert = ''; @mysql_query("UPDATE $this->table SET active=NOT active where id=$id" ); if (mysql_error()) $alert = "&_alert=Error trying to toggle menu item"; header("Location: $ref$alert"); exit; } } ?>
Simple, isn't it? We simply take the GET variable and update the respective record! Notice how we notify the user with an alert if something goes wrong. Now the "move" code which is almost as simple. There might be quicker ways of doing the following but being more of a moron than a mastermind I came up with the following:
<?php class MenuEditor_Moveitem_module extends CmsModule { var $table = "simple_menuitems"; function init(){ parent::init(); if ($this->vars['table'] != ""){ $this->table = $this->vars['table']; } } function fetch(){ $dir = $_GET['dir']; $id = $_GET['id']; $ref = $_SERVER['HTTP_REFERER']; //get current idx $idxquery = "SELECT idx FROM $this->table WHERE id=$id"; $idx = getSQLvalue($idxquery); switch($dir){ case "up": // get list of all items with idx lower or equal to ours $query = "SELECT id,idx FROM $this->table " . "WHERE idx <= $idx ORDER BY idx DESC"; //die($query); $result = mysql_query($query); // just one single (this item) so quit if (mysql_num_rows($result) <=1 ) break; $thisItem = mysql_fetch_object($result); $upperItem= mysql_fetch_object($result); mysql_query("UPDATE $this->table SET idx=$thisItem->idx " . "WHERE id=$upperItem->id"); mysql_query("UPDATE $this->table SET idx=$upperItem->idx " . "WHERE id=$thisItem->id"); break; case "dn"; // get list of all items with idx lower or equal to ours $result = mysql_query("SELECT id,idx FROM $this->table " . "WHERE idx >= $idx ORDER BY idx ASC"); // just one single (this item) so quit if (mysql_num_rows($result) <=1 ) break; $thisItem = mysql_fetch_object($result); $lowerItem= mysql_fetch_object($result); mysql_query("UPDATE $this->table SET idx=$thisItem->idx " . "WHERE id=$lowerItem->id"); mysql_query("UPDATE $this->table SET idx=$lowerItem->idx " . "WHERE id=$thisItem->id"); break; } header("Location: $ref"); exit; } } ?>
Now it is up to you to find out what problems there are. You may have wrong table names, wrong module names, wrong paths to modules and all sorts of funny things. In most cases it all goes down to naming conventions.
The validity of this site may vary while it is being
developed.
Feel free to test it, though :)