Below is one way to create dynamic menus in CakePHP. It uses an element, a custom helper and a database table setup for the Tree behavior.
It uses CakePHP 2.6.3.
Since posting the below I have made some improvements and posted them to github ==> https://github.com/jmcd73/superfish-db-menus
Caveats:
- The custom helper "MenuHelper.php" contains eval statements ... used to parse a text array('controller'=>'', 'action'=>'') into PHP. Evals are really a bad idea. If you are thinking of taking user input change the code to be safer. (I have tried to mitigate by checking that the input is an array)
- You would need some extra code to make things on the same menu level be sorted in the correct order
- Use the superfish samples to help you
- I'm really tired and I have stayed up way too late posting this.
Create a database that contains a table as follows
CREATE TABLE `menus` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) DEFAULT NULL, `controller_action_value` varchar(100) DEFAULT NULL, `title` varchar(100) DEFAULT NULL, `lft` int(11) DEFAULT NULL, `rght` int(11) DEFAULT NULL, `created` datetime DEFAULT NULL, `modified` datetime DEFAULT NULL, `sort` int(11) DEFAULT NULL, `active` tinyint(1) DEFAULT NULL, `parent_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Once you have created the database schema. Save it so you can restore it or move it to another app when needed
cd app/ Console/cake schema generate # schema.php will be written to app/Config/Schema cd Config/Schema ../../Console/cake schema dump --write filename.sql # write an SQL file too so you can just go mysql -u menudb -p mendbpw menudb < filename.sql
Use app/Console/cake to bake the MVC
cd app/ Console/cake bake all
Then create the custom helper in app/View/Helper/MenuHelper.php
<?php App::uses('AppHelper', 'View/Helper'); class MenuHelper extends AppHelper { public $helpers = array('Html'); public function buildMenu($menu_array, &$menu = null) { foreach($menu_array as $key => $value) { if ( is_array($value) && array_key_exists('Menu', $value)){ $menu .= '<li>'; $url = "#"; // make sure it's a routing array and not malicious code // eval be bad $pattern = '/^\s*array\s*\(.*(\'|\")(controller|action)(\'|\").*\)\s*$/'; if (preg_match($pattern, $value['Menu']['controller_action_value'])){ $url = eval( "return " . $value['Menu']['controller_action_value'] . ";" ); } // attribute array $pattern = '/^\s*array\s*\(.*\)\s*$/'; $options = array(); if (preg_match($pattern, $value['Menu']['title'])){ $options = eval( "return " . $value['Menu']['title'] . ";" ); } $menu .= $this->Html->link($value['Menu']['name'], $url, $options); # echo '<li><a href="#">' . $value['Menu']['name'] . '</a>'; } if ( is_array($value) && array_key_exists('children', $value)){ if (!empty($value['children'])) { $menu .= '<ul>'; $this->buildMenu($value['children'], $menu); $menu .= '</ul>'; } } $menu .= '</li>'; } return $menu; } }
In app/View/Element/Menu create the element
<?php if (!isset($menu_tree) || empty($menu_tree)) : $menu_tree = $this->requestAction('/menus/build_menu'); endif; ?> <ul class="sf-menu"> <?php echo $this->Menu->buildMenu($menu_tree); ?> </ul>
In app/Controller/MenusController.php add the following
public function build_menu() { $this->Menu->recursive = -1; $options = array( 'conditions' => array( 'Menu.active' => 1 ) ); return $this->Menu->find('threaded', $options); }
Download Superfish from http://users.tpg.com.au/j_birch/plugins/superfish/download/
Unzip it and copy the superfish-master/src/js and superfish-master/src/css folders to app/webroot/ and merge them with the existing js and css folders.
Create a init file in app/webroot/js/superfish-init.js
(function ($) { //create closure so we can safely use $ as alias for jQuery $(document).ready(function () { // initialise plugin $('ul.sf-menu').superfish({ //add options here if required }); }); })(jQuery);
In app/View/Layouts/default.ctp add the superfish css and js along with the init file
<head> // ... snipped ... // <?php echo $this->Html->css(array( 'cake.generic', 'superfish', )); $this->Html->script( array( 'jquery.js', 'hoverIntent', 'superfish', 'superfish-init' ), array( 'block' => 'script_bottom', 'inline' => false )) ?> </head> <body> <div id="header"> <!-- replace the heading --> <?php echo $this->Element('Menus/menu.ctp'); ?> </div> // ... snipped ... // <?php echo $this->fetch('script_bottom'); ?> </body> </html>
Navigate to menus/add and create a top level menu. The settings as shown will generate <a href="#" title="Top Level menu with # link">Top</a>
Once you save the menu you should see a menu button appear
With the edit and add baked CRUD views you may have to add:
// app/Controller/MenusController.php public function add() { if ($this->request->is('post')) { $this->Menu->create(); if ($this->Menu->save($this->request->data)) { $this->Session->setFlash(__('The menu has been saved.')); return $this->redirect(array('action' => 'index')); } else { $this->Session->setFlash(__('The menu could not be saved. Please, try again.')); } } // add this $parentMenus = $this->Menu->ParentMenu->find('list'); $this->set(compact('parentMenus')); } public function edit($id = null) { if (!$this->Menu->exists($id)) { throw new NotFoundException(__('Invalid menu')); } if ($this->request->is(array('post', 'put'))) { if ($this->Menu->save($this->request->data)) { $this->Session->setFlash(__('The menu has been saved.')); return $this->redirect(array('action' => 'index')); } else { $this->Session->setFlash(__('The menu could not be saved. Please, try again.')); } } else { $options = array('conditions' => array('Menu.' . $this->Menu->primaryKey => $id)); $this->request->data = $this->Menu->find('first', $options); } // add this $parentMenus = $this->Menu->ParentMenu->find('list'); $this->set(compact('parentMenus')); } // app/View/Menus/add.ctp and edit.ctp change the parent_id to pickup the values // need empty so you can create multiple top level menus echo $this->Form->input('parent_id' , array( 'empty' => '(Choose a parent)', 'options' => $parentMenus)); // app/Model/Menu.php // because otherwise it automagically picks 'title' as the displayField public $displayField = 'name';
Add another menu...
And now you have multi level dynamic javascript menus
Adding a sub menu
With an argument... because of using array()
0 Comments