tabbed browsing of categories

Random stuff about serendipity. Discussion, Questions, Paraphernalia.
garvinhicking
Core Developer
Posts: 30022
Joined: Tue Sep 16, 2003 9:45 pm
Location: Cologne, Germany
Contact:

Post by garvinhicking »

MSB, you'll have to build a smarty function call wrapper around the serendipity_fetchCategories() function in your template config.inc.php and then use that function to fetch the categories. That's a bit of work though.

Best regards,
Garvin
# Garvin Hicking (s9y Developer)
# Did I help you? Consider making me happy: http://wishes.garv.in/
# or use my PayPal account "paypal {at} supergarv (dot) de"
# My "other" hobby: http://flickr.garv.in/
carl_galloway
Regular
Posts: 1331
Joined: Sun Dec 04, 2005 5:43 pm
Location: Andalucia, Spain
Contact:

Post by carl_galloway »

ok, I'm working on option three of Garvin's original reply. I've had some success but need a fresh perspective. I want to use option 3 because I'm creating a new theme and want to make sure the categories will work the way I want it to, not the way s9y currently makes it work.

I copied Garvin's original code, and discovered it doesn't handle child categories. Ok, not a problem I thought, just see if you can figure out the smarty code and that will fix it. Except it hasn't.

My config.inc.ph looks like this;

Code: Select all

<?php
$probelang = dirname(__FILE__) . '/lang_' . $serendipity['lang'] . '.inc.php';
if (file_exists($probelang)) {
    include $probelang;
} else {
    include dirname(__FILE__) . '/lang_en.inc.php';
}

$serendipity['smarty']->assign('CONST', get_defined_constants());

function smarty_getCategories($params, &$smarty) {
    $cats = serendipity_walkRecursive(serendipity_fetchCategories(), 'categoryid', 'parentid', VIEWMODE_LINEAR);
    $returncats = array();
    foreach($cats AS $cat) {
        if ($cat['depth'] > 0) continue;
        $cat['url']           = serendipity_categoryURL($cat, 'serendipityHTTPPath');
        $cat['category_name'] = htmlspecialchars($cat['category_name']);
        $returncats[] = $cat;
    }

    return $returncats;
}

$serendipity['smarty']->register_function('smarty_getCategories', 'smarty_getCategories');
$serendipity['smarty']->assign('category', $serendipity['GET']['category']);
$serendipity['smarty']->assign('categories', smarty_getCategories(null, $serendipity['smarty']));
?>
I had to change the viewmode to linear otherwise the child categories don't appear at all. The index.tpl has this code;

Code: Select all

<div class="tabmenu">
	<ul>{foreach from=$categories item="ccategory"}
		{foreach from="ccategory" item="parentid"}{if $ccategory.parentid == 0}<li><a href="{$ccategory.url}">{$ccategory.category_name}</a></li>
		{elseif $ccategory.parentid != 0}<ul><li><a href="{$ccategory.url}">{$ccategory.category_name}</a></li></ul>{/if}
		{/foreach}
{/foreach}</ul>
</div>
I'm not fussed about styling the html yet so no stylesheet, but I will be using the unordered list to create a dropdown menu, with parent categories visible, and child categories only visible when the parent is hovered over.

I've included three images so you can see what the database looks like, and what result I'm getting when using this code. I believe the problem is my second foreach loop. I've gone through the smarty manual, and I've read the code over and over and just can't figure out what I'm missing unless my variable name is wrong.

This is my database (phpmyadmin)Image

This is the s9y admin screen Image

This is the output in the blog Image

Any advice would be really appreciated.
Carl
garvinhicking
Core Developer
Posts: 30022
Joined: Tue Sep 16, 2003 9:45 pm
Location: Cologne, Germany
Contact:

Post by garvinhicking »

Hi Carl!

Guess what, I'm short on time. :-D

So I'll stay brief, and hope it helps you out.

Your problem is that you expect the parentid to be not the same than "0" and then you start a new <ul> container. But the array of categories is only one dimensional, and not properly sorted the way you want to output it. Thus it stacks ALL Second level items regardless to their parent after the first li, which I guess is not what you might want.

Try this PHP Code:

Code: Select all

<?php
$probelang = dirname(__FILE__) . '/lang_' . $serendipity['lang'] . '.inc.php';
if (file_exists($probelang)) {
    include $probelang;
} else {
    include dirname(__FILE__) . '/lang_en.inc.php';
}

function smarty_getCategories($params, &$smarty) {
    $cats = serendipity_walkRecursive(serendipity_fetchCategories(), 'categoryid', 'parentid', VIEWMODE_LINEAR);
    $returncats = array();
    foreach($cats AS $cat) {
        $cat['depth']         = $cat['depth'];
        $cat['url']           = serendipity_categoryURL($cat, 'serendipityHTTPPath');
        $cat['category_name'] = htmlspecialchars($cat['category_name']);
        $returncats[] = $cat;
    }

    return $returncats;
}

$serendipity['smarty']->register_function('smarty_getCategories', 'smarty_getCategories');
$serendipity['smarty']->assign('categories', smarty_getCategories(null, $serendipity['smarty']));
?> 
(note I got rid of the CONST assignment; this is only overhead and no longer required. Also note that the "category" assignment since serendipity 0.9.1 is no longer required and you can use {$category} immediately.)

Previously, the foreach loop stopped on all categories whose depth was 0. In your case, you DO want all categories with a higher depth.

After that, you should be able to use a HTML code like this:

Code: Select all

<div class="tabmenu">
   <ul>

   {foreach from=$categories item="ccategory"}

      <li class="level_{$ccategory.depth}"><a href="{$ccategory.url}">{$ccategory.category_name}</a></li>

    {/foreach}

    </ul>
</div> 
HTH,
Garvin
# Garvin Hicking (s9y Developer)
# Did I help you? Consider making me happy: http://wishes.garv.in/
# or use my PayPal account "paypal {at} supergarv (dot) de"
# My "other" hobby: http://flickr.garv.in/
carl_galloway
Regular
Posts: 1331
Joined: Sun Dec 04, 2005 5:43 pm
Location: Andalucia, Spain
Contact:

Post by carl_galloway »

Garvin I may not have explained myself well. I actually want the child categories to be sorted under their parent so that the top level list items (the parents) make up the visible menu, and the child categories are underneath these in the dropdown which are only visible when hovered over. The changes you've recommended strip out the next level list items so everything sits on one level.

When I view the source using your code that every catagory is given a depth level of 0. I can't use a smarty {foreach} or {if} statement to sort these into parent and child if they all have the same depth.

Carl
garvinhicking
Core Developer
Posts: 30022
Joined: Tue Sep 16, 2003 9:45 pm
Location: Cologne, Germany
Contact:

Post by garvinhicking »

Hm, okay. I did not really test this. I can look into this at the end of next week, I guess. You might need to remind me, if Judebert or somebody else doesn't solve it for you. :)

Regards,
Garvin
# Garvin Hicking (s9y Developer)
# Did I help you? Consider making me happy: http://wishes.garv.in/
# or use my PayPal account "paypal {at} supergarv (dot) de"
# My "other" hobby: http://flickr.garv.in/
carl_galloway
Regular
Posts: 1331
Joined: Sun Dec 04, 2005 5:43 pm
Location: Andalucia, Spain
Contact:

Post by carl_galloway »

ok, no problem.

Where oh where is SuperJude our hero? He's here, he's there, he's everywhere, oh look, is it a bird, is it a plane, not its.... another author may have to continue these poetic ramblings...
judebert
Regular
Posts: 2478
Joined: Sat Oct 15, 2005 6:57 am
Location: Orlando, FL
Contact:

Post by judebert »

:P SuperJude is off fighting the Zerg. Press one to leave a message, or two to page.

Actually, I'm dealing with family obligations. One of my daughters was participating in a creativity contest called Odyssey of the Mind; we've just had our last meeting (an ice cream party), so that's over. Boy, did that ever suck up the time.

Then there were birthdays, friends, and today: DisneyWorld! We just got annual passes. Sometimes I love living in Orlando.

I'm sorry I haven't been as active here as I would like. I'll try to get back on it. (Ooooh, something shiny! No, no, focus!)
Judebert
---
Website | Wishlist | PayPal
judebert
Regular
Posts: 2478
Joined: Sat Oct 15, 2005 6:57 am
Location: Orlando, FL
Contact:

Post by judebert »

Sigh. I've run into this problem before. The biggie is that Smarty doesn't recurse well: you can't make a function inside the template that calls itself.

What we need is to get the categories as arrays, where the parent category includes an array of children. I thought it would be as simple as copying the plugin_category.tpl, but that just uses a variable saying how far to indent.

That said, I'm looking at the code, and serendipity_walkRecursive() should return the categories in the appropriate order, and set a 'depth' variable to tell us how far down we are. We can utilize this as a flag to tell us when to switch from <li> to <ul>. For instance, the top-level links are depth == 0; when one has a subcategory, the next link is depth == 1; when one of those has a subcategory, the next link is depth == 2; when we exit from that subcategory, the next link is either depth == 2 or depth == 1, depending on how far we exited.

I think, but have not tested, that this template code will work for you:

Code: Select all

<div class="tabmenu">
   <ul>
   {* Set an impossible value for previous depth, so depth == 0 starts a new UL *}
   {assign var="lastdepth" value="-1"}
   {foreach from=$categories item="ccategory"}
       {if $ccategory.depth > $lastdepth}
           {* We just skipped into a subcategory.  We need a new UL inside the parent's LI. *}
           <ul>
             <li class="level_{$ccategory.depth}">
               <a href="{$ccategory.url}">{$ccategory.category_name}</a>
             {* We can't end the LI yet!  What if it has subcategories? *}
           {* Remember how far down we are *}
           {assign var="lastdepth" value="$ccategory.depth"}
       {elseif $ccategory.depth < $lastdepth}
           {* We just exited from a child category.  We need to end the last child's LI, the subcategory UL, and the parent's LI.  For each level.*}
           {section name="surface" start=$ccategory.depth loop=$lastdepth step=1}
             </li>
           </ul>
           </li>
           {/section}
           <li class="level_{$ccategory.depth}">
             <a href="{$ccategory.url}">{$ccategory.category_name}</a>
           {* Can't end the LI yet!  What if it has subcategories? *}
           {assign var="lastdepth" value="$ccategory.depth"}
        {else}
           {* This link is on the same level.  We need to end the previous link's LI before adding our (unterminated) LI. *}
           </li>
           <li class="level_{$ccategory.depth}">
             <a href="{$ccategory.url}">{$ccategory.category_name}</a>
           {* Still can't terminate LI!  Might have subcategories! *}
           {assign var="lastdepth" value="$ccategory.depth"}
       {/if}
    {/foreach}
    {* Terminate the ULs and LIs we're in. *}
    {section name="surfaceall" start=0 loop=$lastdepth step=1}
      </li>
    </ul>
    {/section}
</div> 
Of course, we could also create a new array as we went, or something. We may have to, if this doesn't work. Let me know.
Judebert
---
Website | Wishlist | PayPal
carl_galloway
Regular
Posts: 1331
Joined: Sun Dec 04, 2005 5:43 pm
Location: Andalucia, Spain
Contact:

Post by carl_galloway »

Jude you are amazing. And I actually follow all you logic which is even more amazing. Haven't tested this yet, but if it works, I'll be singing your praises from the highest belfry tower.

Carl
carl_galloway
Regular
Posts: 1331
Joined: Sun Dec 04, 2005 5:43 pm
Location: Andalucia, Spain
Contact:

Post by carl_galloway »

sorry jude, this didn't work, or I have not understood how to implement this. I copied your code in its entirety to my index.tpl but every category still has a depth of zero, so they all still appear on the same level within the list.

Carl
judebert
Regular
Posts: 2478
Joined: Sat Oct 15, 2005 6:57 am
Location: Orlando, FL
Contact:

Post by judebert »

Rats. Maybe walkRecursive isn't doing what I want it to do.

Let me fool with it a little more; I'll post again soon.
Judebert
---
Website | Wishlist | PayPal
judebert
Regular
Posts: 2478
Joined: Sat Oct 15, 2005 6:57 am
Location: Orlando, FL
Contact:

Post by judebert »

Okay, Carl, here come the goodies!

Actually, not as good as I want. The parents should marked in walkRecursive. I'll try that out later, and see about getting it into the core.

Meanwhile, this creates the nested list you're looking for, with markup better than you expected.

config.inc.php:

Code: Select all

<?php
$probelang = dirname(__FILE__) . '/lang_' . $serendipity['lang'] . '.inc.php';
if (file_exists($probelang)) {
    include $probelang;
} else {
    include dirname(__FILE__) . '/lang_en.inc.php';
}

$serendipity['smarty']->assign('CONST', get_defined_constants());

function smarty_getCategories($params, &$smarty) {
    $allcats = serendipity_fetchCategories();
    $cats = serendipity_walkRecursive($allcats, 'categoryid', 'parentid', VIEWMODE_THREADED);
    $returncats = array();
    $prevx = -1;
    $lastdepth = 0;
    foreach($cats AS $cat) {
        if ($cat['depth'] > $lastdepth) {
	  $returncats[$prevx]['has_children'] = true;
	}
	$lastdepth = $cat['depth'];
        $cat['url']           = serendipity_categoryURL($cat, 'serendipityHTTPPath');
        $cat['category_name'] = htmlspecialchars($cat['category_name']);
        $returncats[] = $cat;
	$prevx++;
    }

    return $returncats;
}

$serendipity['smarty']->register_function('smarty_getCategories', 'smarty_getCategories');
$serendipity['smarty']->assign('category', $serendipity['GET']['category']);
$serendipity['smarty']->assign('categories', smarty_getCategories(null, $serendipity['smarty']));
?> 
You see, we previously continued whenever depth was not 0, skipping all the child categories. This keeps the children around. It also marks parent categories with "has_children", so we can mark the parent link and make the submenu visible.

index.tpl tabbed menu snippet:

Code: Select all

<!-- TABMENU START -->
<div class="tabmenu">
   <ul>
   {assign var="lastdepth" value=0}
   {foreach from=$categories item="ccategory"}
      {if $ccategory.has_children}
         {* Add link and subcategory list *}
         <li class="level_{$ccategory.depth} submenu">
            <a href="{$ccategory.url}">{$ccategory.category_name}</a>
            {* We can't end the LI yet!  It has subcategories! *}
            <ul>
            {* Remember how far down we are *}
            {assign var="lastdepth" value=$ccategory.depth}
      {else}
         {if $ccategory.depth < $lastdepth}
            {* We just exited from a subcategory.  We need to end the subcategory UL and the parent's LI.  For each level.*}
            {section name="surface" start=$ccategory.depth loop=$lastdepth step=1}
               </ul>
            </li>
            {/section}
            <li class="level_{$ccategory.depth}">
               <a href="{$ccategory.url}">{$ccategory.category_name}</a>
	    </li>
            {assign var="lastdepth" value=$ccategory.depth}
         {else}
            {* We have no subcategories, but we didn't just exit from a subcategory either.  We're an LI in a bigger list. *}
            <li class="level_{$ccategory.depth}">
               <a href="{$ccategory.url}">{$ccategory.category_name}</a>
            </li>
            {assign var="lastdepth" value=$ccategory.depth}
	 {/if}
      {/if}
   {/foreach}
   {* Terminate the ULs and LIs we're in. *}
   {section name="surfaceall" start=0 loop=$lastdepth step=1}
      </ul>
   </li>
   {/section}
</div> 
<!-- TABMENU END -->
Our previous code created an imaginary parent category to create the first UL; now we just start with the top-level UL. We marked all the actual parent categories earlier, so we use that mark now to create a UL and mark the parent category with class "submenu". We still have to keep track of our depth, so when we exit from a subcategory we know how far back to go. We also get to terminate our LIs as soon as possible, since we know which ones have children and which ones don't.

I tested it on my testbed site with multiple category configurations, but I still might have missed something. If you find a problem, let me know.

Good Luck!
Judebert
---
Website | Wishlist | PayPal
carl_galloway
Regular
Posts: 1331
Joined: Sun Dec 04, 2005 5:43 pm
Location: Andalucia, Spain
Contact:

Post by carl_galloway »

Jude, that is brilliant, it works exactly how I wanted it to, in fact exactly the way the original author of this thread wanted it as well. Thank you.

I notice in another thread that Garvin comments on the speed of smarty vs raw php, I think it was in relation to the html output of the archive plugin. Could someone tell me, is smarty really slower? I was under the impression that it was only slower the very first time the smarty code is parsed when it gets converted into raw php and a copy of this stuck into the template_c folder. Am I wrong? Should I avoid using smarty for this kind of situation?

Carl
judebert
Regular
Posts: 2478
Joined: Sat Oct 15, 2005 6:57 am
Location: Orlando, FL
Contact:

Post by judebert »

Smarty looks to me like a thin wrapper over PHP. I could be wrong, though. I know Garvin does recommend using PHP for things that'll called regularly, so they complete as quickly as possible. Maybe he'll tell us something a little more in-depth here?
Judebert
---
Website | Wishlist | PayPal
garvinhicking
Core Developer
Posts: 30022
Joined: Tue Sep 16, 2003 9:45 pm
Location: Cologne, Germany
Contact:

Post by garvinhicking »

I wish I could, I never dug much into the smarty API.

But the PHP code that is used still requires all the code calls to plain PHP, so it is an overhead, since it requires all Smarty-Functions being called and returning their function call statements.

I don't know much more about that. IT's on my todo list for when I ever have spare time ;)

Regards,
Garvin
# Garvin Hicking (s9y Developer)
# Did I help you? Consider making me happy: http://wishes.garv.in/
# or use my PayPal account "paypal {at} supergarv (dot) de"
# My "other" hobby: http://flickr.garv.in/
Post Reply