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