Auto Breadcrumbs

Starting from the Cookbook Breadcrumb example, I modified that by adding the following features:

  • Automatically tracks a user history since visiting the homepage. You no longer need to establish the bread crumb list for every view.

  • The breadcrumb name can either be set from either the page title or via a crumb title property.

  • The widget is established in the main layout view. So you don’t need to put the widget in every view. Just set it in the main layout once, and you’ll have breadcrumbs on every page.

  • Will truncate the breadcrumb list if it gets too long.

  • You can exclude certain pages (like the Login page) from showing up in the breadcrumb list.

I’ve only been using Yii for a couple of days, so there might be ways to improve this. Please feel free to share your comments. Enjoy!

components/BreadCrumb.php:




<?php

class BreadCrumb extends CWidget {


    public $crumbs = array();

    public $newCrumb =array();

    public $delimiter = ' &rarr ';

    public $hideCrumbsOnHome = true;

    public $firstCrumb = array('Home' => array('name' => 'Home', 'url' => array('site/index')));

    public $excludeCrumbs = array('Login');

    public $crumbs2Show = 3;

    public $truncatedCrumb = array('Truncated' => array('name' => '...'));


    public function run() {


        // Breadcrumbs are a way back to to the homepage so dump

        // the crumbs if we find ourselves back on the homepage

        $homepageRoutes = array('/index.php','/site/index','/');

        if ( in_array($this->newCrumb['url'][0], $homepageRoutes)) {

            unset ($_SESSION['crumbs']);


            // If desired, don't show the lone Home crumb on

            // the homepage

            if ($this->hideCrumbsOnHome) {

                return;

            }

        }


        // Place the homepgage anchor crumb in the first position

        $this->crumbs = $this->firstCrumb;


        // Some pages, such as Login, we don't want in the list, so

        // let's exclude them

        if ( !in_array($this->newCrumb['name'], $this->excludeCrumbs)) {




            $newCrumbKey = $this->newCrumb['name'];


            // If we have an existing crumb list, check to see whether

            // the new crumb is already in the list. If so, dump all the

            // crumbs from that crumb position to the end of the list. The

            // purpose of this is to keep the list clean of duplicates.

            if ( sizeof($_SESSION['crumbs']) > 0 ) {

                if ( array_key_exists($newCrumbKey, $_SESSION['crumbs'])) {


                    $offset = $this->array_offset($_SESSION['crumbs'], $newCrumbKey);

                    $_SESSION['crumbs'] = array_slice( $_SESSION['crumbs'], 0, $offset, true);


                }

            }


            // Finally add the new crumb to the end of the list

            $_SESSION['crumbs'][$newCrumbKey]=$this->newCrumb;


            // If we have more crumbs than we want to display, we'll evict the

            // oldest crumbs from the list. Plus we'll show a truncated crumb

            // so the user has a visual indicator that we are truncating.

            if (sizeof($_SESSION['crumbs']) > $this->crumbs2Show ) {

                array_shift($_SESSION['crumbs']) ;

                $this->crumbs = array_merge($this->crumbs, $this->truncatedCrumb);

            }

        }


        // Ok, we've build the crumb list prefix with the Home crumb and possibly

        // the Truncated crumb. Now lets add the user's crumbs.

        if ( sizeof($_SESSION['crumbs']) > 0 ) {

            $this->crumbs = array_merge($this->crumbs, $_SESSION['crumbs']);

        }


        // display!

        $this->render('breadCrumb');

    }


    /**

     * Find the integer position of the offset key in the array

     * @param array $array

     * @param string $offset_key

     * @return int

     */

    public function array_offset($array, $offset_key) {

        $offset = 0;

        foreach($array as $key=>$val) {

            if($key == $offset_key)

                return $offset;

            $offset++;

        }

        return -1;

    }

}

?>



components/views/breadCrumb.php:




<div id="breadCrumb">

    <?php


    $lastCrumb = array_pop($this->crumbs);

    

    foreach($this->crumbs as $crumb) {

        

        if(isset($crumb['url']) && !$isLastCrumb) {

            echo CHtml::link($crumb['name'], $crumb['url']);

        } else {

            echo $crumb['name'];

        }


        echo $this->delimiter;


    }

    echo $lastCrumb['name'];

    ?>

</div>



views/layouts/main.php

Note: You can set the breadcrumb title from one of two properties: either the page title property built into CControler or by a bread crumb title property that you will need to add into the relevant controller. Here’s the steps needed to handle each scenario:

  • pageTitle:

[list=1]

  • In the controller actions, set the page title before rendering like so: $this->setPageTitle(‘Product #1022 Blue Bag with Shoulder Strap’)

[*]crumbTitle: Sometimes, you want the breadcrumb to be shorter or different than the page title. Setting this property will allow for that.

[list=1]

[*]Add the crumbTitle property to the controller class like so: public $crumbTitle;

[*]In the controller’s actions, set the crumbTitle like so: $this->crumbTitle = ‘Product #1022

[/list]

[/list]




<div id="breadcrumbs">

<?php $this->widget('application.components.BreadCrumb', array(

  'newCrumb' =>

    array('name' => isset($this->crumbTitle)?$this->crumbTitle:$this->getPageTitle(), 'url' => array($_SERVER['REQUEST_URI']))

)); ?>

</div>



cool! this is much better than manually creating each breadcrumb.

what i think would be more elegant though is if the web of views was defined somewhere (if it supported wildcards like the url manager)

I believe some kind of variables and methods are missing on the documentation side, as while running the script, I am getting error related to


$_SESSION['crumbs']

and


$isLastCrumb

.

I’ve made some fixes to nickelstar’s code.

These are:

  • Fixed undefined variables (see Rajesh post).

  • Fixed ‘UrlManager->urlSuffix’ duplication issue when ‘urlSuffix’ is supplied.

  • First (home) breadcrumb now points to ‘UrlManager->baseUrl’ when is url is not supplied.

  • Introduced new property ‘firstCrumbName’ - easy way to set first crumb link text (see widget init example below).

Widget init example (eg. for views/layouts/main.php)




<div id="breadcrumbs">

    <?php

    $this->widget('application.components.BreadCrumb', array(

      'firstCrumbName' => 'The Beginning',

      'newCrumb' =>

	array(

	    'name' => isset($this->crumbTitle)?$this->crumbTitle:$this->getPageTitle(),

	    'url' => array(Yii::app()->getRequest()->requestUri),

	    )

    )); ?>

</div>



There are still many places in the code to improve ;)

The breadcrumb breaks if the application is in a sub-directory:

index.php?r=subdirectory/index.php?r=admin

rather than:

index.php?r=admin

Yeah, it is breaking when creating Url in a subdirectory.

{ trying to fix it }

Getting the same error as rajash

The Issue of undefined variables such as $_SESSION can be resolved by declaring a local variable of type array to store the data.

I have done it as




<?php

class BreadCrumb extends CWidget {


    public $crumbs = array();

    public $newCrumb = array();

    public $delimiter = ' &rarr; ';

    public $hideCrumbsOnHome = true;

    public $firstCrumbName = false;

    public $firstCrumb = array('Home' => array('name' => 'Home', 'url' => array()));

    public $excludeCrumbs = array('Login');

    public $crumbs2Show = 4;

    public $truncatedCrumb = array('Truncated' => array('name' => '…'));




    public function run() {

	$session_crumb=array();

	// if home url is not supplied, use application base url

	if (count($this->firstCrumb['Home']['url'])==0)

	    $this->firstCrumb['Home']['url'] = Yii::app()->UrlManager->baseUrl."/";


	if ($this->firstCrumbName) $this->firstCrumb['Home']['name']=$this->firstCrumbName;


        // Breadcrumbs are a way back to to the homepage so dump

        // the crumbs if we find ourselves back on the homepage

        $homepageRoutes = array('/index.php', '/'.Yii::app()->defaultController.'/list', '/');

	

        if ( in_array($this->newCrumb['url'][0], $homepageRoutes)) {

            unset ($session_crumb['crumbs']);


            // If desired, don't show the lone Home crumb on

            // the homepage

            if ($this->hideCrumbsOnHome) {

                return;

            }

        }


        // Place the homepgage anchor crumb in the first position

        $this->crumbs = $this->firstCrumb;


        // Some pages, such as Login, we don't want in the list, so

        // let's exclude them

        if ( !in_array($this->newCrumb['name'], $this->excludeCrumbs)) {


            $newCrumbKey = $this->newCrumb['name'];


	    if (!key_exists('crumbs', $session_crumb)) $session_crumb['crumbs'] = array();


            // If we have an existing crumb list, check to see whether

            // the new crumb is already in the list. If so, dump all the

            // crumbs from that crumb position to the end of the list. The

            // purpose of this is to keep the list clean of duplicates.

            if ( sizeof($session_crumb['crumbs']) > 0 ) {

                if ( array_key_exists($newCrumbKey, $session_crumb['crumbs'])) {


                    $offset = $this->array_offset($session_crumb['crumbs'], $newCrumbKey);

                    $session_crumb['crumbs'] = array_slice( $session_crumb['crumbs'], 0, $offset, true);


                }

            }


	    // Handle UrlManager->urlSuffix case

	    $this->newCrumb['url'][0] = rtrim($this->newCrumb['url'][0], Yii::app()->UrlManager->urlSuffix);

            // Finally add the new crumb to the end of the list

            $session_crumb['crumbs'][$newCrumbKey]=$this->newCrumb;


            // If we have more crumbs than we want to display, we'll evict the

            // oldest crumbs from the list. Plus we'll show a truncated crumb

            // so the user has a visual indicator that we are truncating.

            if (sizeof($session_crumb['crumbs']) > $this->crumbs2Show ) {

                array_shift($session_crumb['crumbs']) ;

                $this->crumbs = array_merge($this->crumbs, $this->truncatedCrumb);

            }

        }


        // Ok, we've build the crumb list prefix with the Home crumb and possibly

        // the Truncated crumb. Now lets add the user's crumbs.

        if ( sizeof($session_crumb['crumbs']) > 0 ) {

            $this->crumbs = array_merge($this->crumbs, $session_crumb['crumbs']);

        }


        // display!

        $this->render('BreadCrumb');

    }


    /**

     * Find the integer position of the offset key in the array

     * @param array $array

     * @param string $offset_key

     * @return int

     */

    public function array_offset($array, $offset_key) {

        $offset = 0;

        foreach($array as $key=>$val) {

            if($key == $offset_key)

                return $offset;

            $offset++;

        }

        return -1;

    }

}

?>



Have any of you had an issue where the array resets each time you navigate to a new page?

For example I am getting

Home -> Page1

Home -> Page2

Home -> Page3

instead of

Home -> Page1 -> Page2 -> Page3

I am using the last set of code from PeRoChAk and the display view/BreadCrumb.php from the original post of nickelstar

Breadcrumbs provide a trail back to the entry point to your site not a history of where the user has been. Therefore what you’re experiencing above is working as it should do. You should use pagination to get around the pages not the breadcrumbs.

My understanding from the original posters functionality this would do what I was looking for since they stated:

So if when a user enters the site and clicks a link to currentSeason then clicks a link to currentPlayers then clicks a link to currentPlayers/edit/5

If I want the user to have the history

Home -> Current Season -> Current Players -> Edit Player 5

what is the proper widget/extension to use? My searches turned up the dynamic breadcrumb and when I used the original post from nickelstar it functioned similarly to what I expected but there appeared to be some problems that were worked out so I changed to PeRoChAk’s code which functions differently.

kellan4459 could you post your code, to understand what you mean?

Thanks