Simple Breadcrumbs Components/Helper
I made this component/helper a while ago for a site i was building and i thought i would share it with you. It's a very simple and straight forward way to add breadcrumbs to your site with minimal mess or fuss.
Component Code (app/controllers/components/breadcrumb.php):
< ?php
/*
Copyright (c) 2009 Matthew Nessworthy - http://www.devmatt.co.za/
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
class BreadcrumbComponent extends Object {
var $controller;
var $breadcrumbs;
//called before Controller::beforeFilter()
function initialize(&$controller, $settings = array()) {
// saving the controller reference for later use
$this->controller =& $controller;
}
//called after Controller::beforeRender()
function beforeRender(&$controller) {
$this->controller->set('__breadcrumbs', $this->breadcrumbs);
}
function add() {
$args = func_get_args();
if(!$args) {
$args = array(
array(
$this->controller->name,
$this->controller->here
)
);
}
foreach($args AS $breadcrumb) {
$this->breadcrumbs[] = $breadcrumb;
}
}
function reset() {
$this->breadcrumbs = array();
}
}
?>
Helper Code (app/views/helpers/breadcrumb.php):
viewVars['__breadcrumbs'])) {
$this->breadcrumbs = $view->viewVars['__breadcrumbs'];
}
}
function generate() {
return $this->output($this->build_nav($this->breadcrumbs));
}
function build_nav($breadcrumbs, $level=0) {
$i = 0;
$count = count($breadcrumbs);
$out = '
- ';
foreach($breadcrumbs AS $breadcrumb) {
$i++;
$breadcrumb[1] = (isset($breadcrumb[1]) && $breadcrumb[1]) ? $breadcrumb[1] : '#';
$breadcrumb[2] = (isset($breadcrumb[2]) && $breadcrumb[2]) ? $breadcrumb[2] : null;
$breadcrumb[3] = (isset($breadcrumb[3]) && $breadcrumb[3]) ? $breadcrumb[3] : null;
$out .= '
- '; $out .= $this->Html->link($breadcrumb[0], $breadcrumb[1], $breadcrumb[2]); if(isset($breadcrumb[3]) && is_array($breadcrumb[3]) && $breadcrumb[3]) { $out .= $this->build_nav($breadcrumb[3], $level+1); } $out .= ' '; } $out .= '
Setup:
- Create a file called breadcrumb.php in your components folder (e.g. app/controllers/components/breakcrumb.php) with the code above.
- Create a file called breadcrumb.php in your helpers folder (e.g. app/views/helpers/breakcrumb.php) with the code above.
- Include the Component in the controllers you want to use it in - i would suggest including it in your AppController - (e.g. var $components = array('Breadcrumb');)
- Include the Helper in the controllers you want to use it in - i would suggest including it in your AppController - (e.g. var $helpers = array('Breadcrumb');)
Usage (Controller Code):
$this->Breadcrumb->add(
array(
'Home', //title
array('controller' => 'pages', 'action' => 'home') //link - array based
),
array(
'Posts',
'/posts/index' //link - string based,
//dropdown links
array(
array(
'Latest',
'/posts/latest'
)
)
)
);
OR
$this->Breadcrumb->add(
array(
'Home',
array('controller' => 'pages', 'action' => 'home')
)
);
$this->Breadcrumb->add(
array(
'Posts',
'/posts/index',
array(
array(
'Latest',
'/posts/latest'
)
)
)
);
Usage (View Code):
The output generated will look a lot like this:
My preferred menu is the excellent Superfish JS/CSS menu package
CakePHP Daemon Shell
A really excellent and useful CakePHP shell task is the Daemon Task. It allows you to run a pseudo daemon process. I say pseudo because the is no child process detached or anything of that sort. It would actually be more accurate to say that it allows you to run 1 instance of a specific task.
This is great to do things like an email sendout where multiple iterations of the same script could potentially cause problems (if for instance you run a cron job to start the script before the previous call to the same script exits). The Daemon Task takes care of this perfectly.
AJAX jQuery link binding
When building an AJAX site often there will be a large amount of links that need to be processed in a specific way. Namely links will need to load content and replace a DOM element.
A very basic way of doing this would be to do the following:
$(document).ready(function() {
$('a').livequery('click', function() {
var $el = $(this);
var href = $el.attr('href');
$('#content').load(href);
return false;
});
});
Now, lets run through this.
$(document).ready(function() {
//snipped code
});
jQuery has a great ability to run as soon as the DOM is ready, not just once it has loaded. Every inside this codeblock will be run as soon as the DOM is ready.
$('a').livequery('click', function() {
var $el = $(this);
var href = $el.attr('href');
$('#content').load(href);
return false;
});
Here we are using the amazing plugin livequery - which allows for binding/firing callbacks on elements loaded after the DOM has loaded (i.e. via AJAX) - to bind the click event on all links. From there we load content via ajax and replace the contents of the DOM element with the id="content".
There are a couple of problems with this method:
- It's slow. It has to find and loop through all links on the site, and everything loaded via AJAX thereafter and bind their click events.
- Because of the time and processing required to bind all the links there will (for a small amount of time) be unbound links and if a user were to click on an unbound link the page will load as per normal, which in the case of a pure AJAX site is abnormal (link unstyled content).
Now, let's look at an alternative solution:
$(document).ready(function() {
$('body').bind('click', function(event) {
var $el;
if($(event.target).is('a')) {
$el = $(event.target);
} else {
$el = $(event.target).parents('a');
}
if($el.is('a')) {
var href = $el.attr('href');
$('#content').load(href);
return false;
}
)};
});
Now, instead of binding all the links in the document we are binding the body element and then checking what element is clicked. If the element is a link then it is processed as per the previous example.
- The benefits of this method is that the livequery plugin is not needed (less additional overhead).
- AJAX loaded content will automatically be bound because its parent element is bound.
- It's fast. With only 1 element to be bound there is very little processing required. The only processing required is when a link is actually clicked.
Well, there you have it, the code is obviously simplified. There are almost limitless possibilities to enhance this code and or refactor it to be used for a different purpose. I'll leave it up to you to use it as you will. If you find any errors or have any suggestions let me know.
Pagination & Containable
Some people seem to have issues with using the Containable (CakePHP 1.2 - built-in) behavior and pagination. A very simple way of getting around this is just to set the last argument to false when setting the model relations via the $this->{ModelName}->contain(); method.
This example will not work:
$this->Post->recursive = -1;
//include approved comments
$this->Post->contain(
array(
'Comment' => array(
'conditions' => array(
'Comment.approved' => true
)
)
)
);
$this->paginate();
The reason the above code will not work is not because the recursive level is set to -1, but because the paginate() function makes 2 calls (count records + retrieve records) and after the first call the model relations get reset. Simply adding a false to the end of the contain() function call, will cause the model relationships to not be reset and hence data will be returned as per your request.
e.g.
$this->Post->recursive = -1;
//include approved comments
$this->Post->contain(
array(
'Comment' => array(
'conditions' => array(
'Comment.approved' => true
)
)
),
false
);
$this->paginate();
Don't forget to reset the model bings after the call if you want to retrieve more information.
$this->Post->resetBindings();
It's as simple as that.