Use treemenu to get whole subtree at once

Author Message

H-Works Agency

Thursday 01 June 2006 1:35:10 pm

How can i use the treemenu function to get the whole subtree at once ?

Because by default this function seems to only display the first depth under current node.

In my case i want the left menu to display ALL depthes under current node : Is it possible ?

I hope so because i don't understand the aim of having only the first depth under current node because that mean that i would have to display content on each clickable node and sometime i have folder that only contains subfolders and no other classes of contents.

Thanx for help, i am on this for monthes and wasn't able to find a decent solution (for the moment i use 'simplified_treemenu.tpl' but i don't have the same attributes to play with...

Martin

EZP is Great

Kristof Coomans

Thursday 01 June 2006 11:40:54 pm

Hi Martin

Did you try to set the max_depth parameter to a value bigger than the maximum depth of your tree?

independent eZ Publish developer and service provider | http://blog.coomanskristof.be | http://ezpedia.org

H-Works Agency

Friday 02 June 2006 12:31:24 pm

Hi Kristof

I tried your proposition but its not changing anything. Have you tried this with success ?

Its really a pitty i can't achieve that with treemenu...i really don't understand the interest of having only the first depth shown.

Do you have another idea :p

Martin

EZP is Great

Kristof Coomans

Saturday 03 June 2006 1:39:06 am

Hi Martin, I think I misunderstood you.

If you click on a node under the current one in your treemenu, then you go to the full view of that subnode. Now the treemenu should also display the children of that subnode. Does it do that?

It's currently not possible with the treemenu operator to immediately display all descendants (instead of only the children) of the current node. But it would be a nice enhancement.

independent eZ Publish developer and service provider | http://blog.coomanskristof.be | http://ezpedia.org

H-Works Agency

Monday 05 June 2006 7:23:10 am

Yes what i want to do is displaying all the descendents of current node. That would allow me to build a dropdown CSS2 menu.

Now i know its not possible with the treemenu function.

Thanx for your help.

EZP is Great

Philipp Simon

Tuesday 04 July 2006 2:02:11 am

Anybody did this? Is there a work-around or a replacement for the treemenu function, which gives me the whole subtree?

Regards

Phil

Pascal France

Wednesday 05 July 2006 12:28:38 am

Hi Martin,

I'm not sure this is what you are looking for, but I did a treemenu you can see here:

http://www.linuxorable.net/ez_publish/index.php/linuxorable

(I'm talking about the dynamic treemenu below the mainmenu)

You can use onclick or onmouseover in the javascript and you can change the CSS.

I think you can use this dynamic treemenu on place of the leftmenu (I didn't try this).

This dynamic treemenu is an improvement of this hack:

http://ez.no/community/contribs/hacks/expandablelist

Regards

Pascal

Ce qui embellit le désert c'est qu'il cache un puits... quelque part... (A. de Saint-Exupéry) - http://luxpopuli.fr/eZ-Publish

Philipp Simon

Thursday 06 July 2006 2:18:22 am

I tried 'expandablelist', using the fetch method to get the whole tree. Unfortunately it seems like it is not possible to have a sorted tree.... when using sorting it destroys the tree structure and sorts all nodes at once. Damn.

Has nobody writing template code which builds/prints a sorted node tree? Does your 'dynamic treemenu' keeps the tree and sorting?

Your site is on French, you mean the "Créer un "DoubleTopMenu" en tant qu'extension"?

Phil

H-Works Agency

Thursday 06 July 2006 2:33:48 am

Hi all,

Yes this is what i am looking for :)

Where can i get you extension ?

Thanx in advance,
Martin

EZP is Great

Nathan Kelly

Thursday 06 July 2006 2:54:28 am

Hi Martin,

I have worked on 2 sites now that require main (top) navigation and sub (bottom) navigation to be on the page at once and here are my solutions (you may need to customise, I have some extra variables etc. in there):

Suckerfish menu (drop down)

				<ul id="main-menu">
{def $home=fetch('content', 'node', hash('node_id', $topnode_id) ) 
		$main_menu=fetch('content', 'list', hash('parent_node_id', 2,
			'sort_by', array( array( 'priority' ) ),
			'class_filter_type', include,
			'class_filter_array', array( 'folder' ) ) ) }
{def $m_count=$main_menu|count|dec()}
{foreach $main_menu as $m_index => $m_menu}

  					<li{if eq($module_result.path[1].node_id,$m_menu.node_id)} class="current"{/if}{if $m_index|eq($m_count)} id="last"{/if}><a href={$m_menu.url_alias|ezurl}><span><span>{$m_menu.name}</span></span></a>
{def $sub_menu=fetch( 'content', 'list', hash( 'parent_node_id', $m_menu.node_id,
			'sort_by', array( array( 'priority' ) ),
			'class_filter_type', include,
			'class_filter_array', array( 'folder', 'link' ) ) ) }
{def $s_count=$sub_menu|count|dec()}			
{if $sub_menu|count|gt(0)}

						<ul class="sub-menu">
{foreach $sub_menu as $s_index => $s_menu}

							<li{if $s_index|eq($s_count)} class="last"{/if}><a href={$s_menu.url_alias|ezurl}>{$s_menu.name}</a></li>
{/foreach}

						</ul>
{/if}

					</li>
{/foreach}
{undef}

				</ul>

Top main nav plus sidebar sub nav:

<!-- top (main nav) -->
				<ul>
{def $home=fetch('content', 'node', hash('node_id', $topnode_id) ) 
		$main_menu=fetch('content', 'list', hash('parent_node_id', 2,
			'sort_by', array( array( 'priority' ) ),
			'class_filter_type', include,
			'class_filter_array', array('folder') ) ) }

{foreach $main_menu as $m_menu}

  					<li><a{if eq($module_result.path[1].node_id,$m_menu.node_id)} class="current"{/if} href={$m_menu.url_alias|ezurl}>{$m_menu.name}</a></li>
{/foreach}
{undef $home}

				</ul>	

<!-- sidebar (sub nav) -->

{def $sub_menu=treemenu($module_result.path, $module_result.node_id,
			array('standard_page','folder','contact_form','sub_page','link'), 1, 2) }

				<ul>
{def $last=0} 
{foreach $sub_menu as $s_menu}
{if and($last|ne(0), $last.level|gt($s_menu.level))}{* this if second level is open *}
					</ul>
					</li>
{/if}

					<li>
{if and($last|ne(0), $last.level|lt($s_menu.level))}{* this if there is no second level *}
					<ul>
					<li>{/if}<a{if $s_menu.is_selected} class="current"{/if} href={$s_menu.url_alias|ezurl}>{$s_menu.text}</a></li>
{set last=$s_menu}
{/foreach}

				</ul>

{undef $last}
{undef $sub_menu}

This code produces the menus found at:
http://www.ccts.com.au/ccts/construction/construction_services

Hope this helps.

Cheers!

Pardon me while I burst into flames...

H-Works Agency

Thursday 06 July 2006 10:44:23 am

Well i tried you code but it still doesn't show the whole subtree at once.

I don't understand the differences between your menu and the default one ?

And i still don't understand why treemenu isn't showing the whole subtree as a default behavior :p Who wants to display 10 pages before arriving to the desired content ?

Usually you display your whole content tree in left menu to allow users to find what they search quicker.

Martin

EZP is Great

Nathan Kelly

Thursday 06 July 2006 5:11:49 pm

Hi Martin,

If you want to display the whole subtree like:

[-] top node
 |--[-] main node1
     |-- node
     |-- node
     |--[-] node
         |-- node
         |-- node
         |--[-] node
             |-- etc etc etc...
|--[-] main node2
    |-- node
    |-- node
    |--[-] node
        |-- etc etc etc
|--[-] etc etc etc

You will need to customise the code I placed above to go deeper than one level.

The difference with the fetches I use to the treemenu fetch is added control, it allows for further refining than the standard treemenu operator offers. I was using the treemenu originally but needed more control and I found that fetching a content list was much more effective.

The treemenu should be configurable to go to the full depth also, have you set the maxdepth? I think you can set the maxdepth higher than needed i.e. if you tree is 6 levels deep, set maxdepth to 10 and it should work its way to the bottom.

Cheers!

Pardon me while I burst into flames...

Philipp Simon

Friday 07 July 2006 12:49:48 am

No, treemenu only goes ONE level down. Currently it's not possible to show the whole subtree with treemenu function. I already tried it. Beside this, treemenu should also give some infos about the content class, cause until now link objects can not be identified and so the url_alias is used. (another fetch node needed to check this)

I will now try my luck to build a menu based on fetch list. Will share when I am ready.

Phil

Philipp Simon

Monday 10 July 2006 3:20:25 am

Hello guys!

I rewrote the treemenu operator from scratch, so it does work recursively now. I tried to keep the old functionality, but "depth_skip" is now longer used. 'node_id' is now really used as start node. And last but not least I added an boolean value 'show_levels' to force the treemenu to show the whole subtree. Here is the code:

class eZTreeMenuOperator
{
    /*!
     */
    function eZTreeMenuOperator( $name = 'treemenu' )
    {
        $this->Operators = array( $name );
    }

    /*!
    */
    function &operatorList()
    {
        return $this->Operators;
    }

    /*!
    */
    function namedParameterList()
    {
        return array( 'path' => array( 'type' => 'array',
                                       'required' => true,
                                       'default' => false ),
                      'node_id' => array( 'type' => 'int',
                                          'required' => true,
                                          'default' => false ),
                      'class_filter' => array( 'type' => 'array',
                                               'required' => false,
                                               'default' => false ),
                      'depth_skip' => array( 'type' => 'int',
                                             'required' => false,
                                             'default' => false ),
                      'max_level' => array( 'type' => 'int',
                                            'required' => false,
                                            'default' => false ),
                      'show_levels' => array( 'type' => 'int',
                                            'required' => false,
                                            'default' => false ),
                      'is_selected_method' => array( 'type' => 'string',
                                                     'required' => false,
                                                     'default' => 'tree' ),
                      'indentation_level' => array( 'type' => 'int',
                                                    'required' => false,
                                                    'default' => 15 ),
                      'language' => array( 'type' => 'string|array',
                                           'required' => false,
                                           'default' => false ) );
    }

    /*!
    */
    function modify( &$tpl, &$operatorName, &$operatorParameters, &$rootNamespace, &$currentNamespace, &$operatorValue, &$namedParameters )
    {
        $pathArray        = array();
        $tmpModulePath    = $namedParameters[ 'path'               ];
        $indentationLevel = $namedParameters[ 'indentation_level'  ];
        $isSelectedMethod = $namedParameters[ 'is_selected_method' ];
        $classFilter      = $namedParameters[ 'class_filter'       ];
        $depthSkip        = $namedParameters[ 'depth_skip'         ];        
        $language         = $namedParameters[ 'language'           ];
        $maxLevel         = $namedParameters[ 'max_level'          ];
        $showLevels       = $namedParameters[ 'show_levels'        ];
        $node_id          = $namedParameters[ 'node_id'            ];        

        if( $classFilter == 'false' )
        {
            $classFilter = array();
        }
        else if ( count( $classFilter ) == 0 )
        {
            $classFilter = array( 1 );
        }

        if ( $node_id === false )
        {
            $node_id = 2;
        }

        if ( $maxLevel === false )
        {
            $maxLevel = 2;
        }

        $cur_node_id = $node_id;
        $cur_node = eZContentObjectTreeNode::fetch( $cur_node_id );
        $cur_node_depth = $cur_node->attribute( "depth" );

        $params = array();
        $params[ 'Depth'            ] = 1;
        $params[ 'Offset'           ] = 0;
        $params[ 'SortBy'           ] = $cur_node->sortArray();
        $params[ 'Language'         ] = $language;
        $params[ 'ClassFilterType'  ] = 'include';
        $params[ 'ClassFilterArray' ] = $classFilter;

        $cur_children = eZContentObjectTreeNode::subTree( $params, $cur_node_id );
        eZContentObject::fillNodeListAttributes( $cur_children );

        $this->traverseChildren( $cur_children, $tmpModulePath, $cur_node_depth, $maxLevel, $showLevels, $isSelectedMethod, $indentationLevel, $params, $pathArray );

        $operatorValue = $pathArray;
    }

    /*!
    */
    function traverseChildren( & $nodeArray, $modulePath, $rootDepth, $maxDepth, $showDepths, $isSelectedMethod, $indentationLevel, $params, & $pathArray )
    {
        $keys = array_keys( $nodeArray );

        foreach ( $keys as $key )
        {
		$absDepth      = $nodeArray[ $key ]->attribute( "depth" );
		$relDepth      = $absDepth - $rootDepth - 1;
            $dataMap       = $nodeArray[ $key ]->attribute( 'data_map' );
            $contentObject = $nodeArray[ $key ]->attribute( 'object' );
            $childrenCount = $nodeArray[ $key ]->attribute( 'children_count' );
            $name          = $nodeArray[ $key ]->attribute( 'name' );
            $tmpNodeID     = $nodeArray[ $key ]->attribute( 'node_id' );
            $url           = "/content/view/full/$tmpNodeID/";
            $urlAlias      = "/" . $nodeArray[ $key ]->attribute( 'url_alias' );
            $mainNodeID    = $nodeArray[ $key ]->attribute( 'main_node_id' );
            $indent        = $indentationLevel * ($relDepth+1);

            $isMainNode = false;
            if( $mainNodeID == $tmpNodeID )
            {
                $isMainNode = true;
            }

            $hasChildren = false;
            if( $childrenCount > 0 )
            {
                $hasChildren = true;
            }

            $isActive = false;
            $isSelected = false;
            if( $modulePath[ $absDepth-1 ][ 'node_id' ] == $tmpNodeID )
            {
                $isActive = true;

                if( $isSelectedMethod == 'tree' || count( $modulePath ) == $absDepth )
                {
                    $isSelected = true;
                }
            }

            $pathArray[] = array( 'id' => $nodeArray [ $key ]->attribute( "node_id" ),
                                  'level' => $relDepth,
                                  'data_map' => $dataMap,
                                  'class_name' => $contentObject->classname(),
                                  'is_main_node' => $isMainNode,
                                  'has_children' => $hasChildren,
                                  'indent' => $indent,
                                  'url_alias' => $urlAlias,
                                  'url' => $url,
                                  'text' => $name,
                                  'is_selected' => $isSelected,
                                  'node' => $nodeArray [ $key ] );

            if( $hasChildren && ( $showDepths || $isActive ) && ( $maxDepth > ($relDepth+1) ) )
            {
                $params[ 'SortBy' ] = $nodeArray[ $key ]->sortArray();
                $subNodeArray = eZContentObjectTreeNode::subTree( $params, $tmpNodeID );
                eZContentObject::fillNodeListAttributes( $subNodeArray );

                if( $subNodeArray && count( $subNodeArray ) > 0 )
                {
                    $this->traverseChildren( $subNodeArray, $modulePath, $rootDepth, $maxDepth, $showDepths, $isSelectedMethod, $indentationLevel, $params, $pathArray );
                }
            }
        }
    }

    /// \privatesection
    var $Operators;
};

And here is my test template for the left menu. I added support for internal as well as external links. (external opens a new window) And I added support for links like "ezNode://66", which will fetch the node and use its url_alias then. So when changing the target url, the link will still be valid.

One word about the root node fetching. My menu looks like this:

- ROOT (ID=2)
- - Home (ID=80)
- - Service
- - - Service 1

So when I am on the ROOT (entry site), I will fetch the menu of the folder "Home". I wanted to keep the url unchanged, but have an own home sidemenu. So whatever you do, you have to tweak this for your own use.

<div id="leftmenu">
<div id="leftmenu-design">
<h3 class="hide">{"Left menu"|i18n("design/base")}</h3>

<div class="toolbox">
<div class="toolbox-design">

{let $root_node=fetch( content, node, hash( node_id, is_set( $module_result.path[1].node_id )|choose( 80, $module_result.path[1].node_id ) ) )}
<h2>{$root_node.name}</h2>
     
<div class="toolbox-content">
{let docs=treemenu( $module_result.path,
                    $root_node.node_id,
                    ezini( 'MenuContentSettings', 'LeftIdentifierList', 'menu.ini' ),
                    0,
                    3,
                    true() )
                    depth=1
                    last_level=0}
<ul>
{section var=menu loop=$:docs last-value}
    {set last_level=$menu.last|is_array|choose( $menu.level, $menu.last.level )}
    {section show=and( $last_level|eq( $menu.level ), $menu.number|gt( 1 ) )}
        </li>
    {section-else}
        {section show=and( $last_level|gt( $menu.level ), $menu.number|gt( 1 ) )}
            </li>
            {"</ul>
            </li>"|repeat(sub( $last_level, $menu.level ))}
        {/section}
    {/section}

    {section show=and( $last_level|lt( $menu.level ), $menu.number|gt( 1 ) )}
        {'<ul><li>'|repeat(sub($menu.level,$last_level,1))}
        <ul>
        <li class="menu-level-{$menu.level}">
    {section-else}
        <li class="menu-level-{$menu.level}">
    {/section}

    {if eq( $menu.class_name, 'Link' )}
        {def $link_content = $menu.node.object.data_map.location.content}
        {if $link_content|contains( 'ezNode' )}
            {def $tmpNodeParts = $link_content|explode( '//' )}
            {def $tmpNodeId = $tmpNodeParts[ 1 ]}
            {let $tmpNode=fetch( content, node, hash( node_id, $tmpNodeId ) )}
                &raquo; <a href={$tmpNode.url_alias|ezurl}>{$menu.text|shorten( 25 )}</a>
            {/let}
            {undef $tmpNodeParts}
            {undef $tmpNodeId}
        {elseif $link_content|contains( 'http://' )}
            &raquo; <a href={$link_content} target="_new">{$menu.text|shorten( 25 )}</a>
        {else}
            &raquo; <a href={$link_content|ezurl}>{$menu.text|shorten( 25 )}</a>
        {/endif}
    {else}
        &raquo; <a {$menu.is_selected|choose( '', 'class="selected"' )} href={$menu.url_alias|ezurl}>{$menu.text|shorten( 25 )}</a>
    {/endif}

    {set depth=$menu.level}
{/section}
</li>

{section show=sub( $depth, 0 )|gt( 0 ) loop=sub( $depth, 0 )}
    </ul>
    </li>
{/section}
</ul>

{/let}
{/let}

</div>
</div>
</div>

</div>
</div>

<b>Although I did a fast test, I am not sure about bugs. So be careful using this code! If you find bugs, please report them to me.</b>

Have fun!

Phil

H-Works Agency

Monday 10 July 2006 7:29:28 am

This looks great Philipp !!!

I am gonna try that right away.

Nevertheless, i hope treemenu operator will be modified to include this feature of getting the whole subtree at once.

Would be even better if you could parameter how much depth "at once" you want to fetch.

Martin

EZP is Great

Philipp Simon

Monday 10 July 2006 7:44:26 am

Hi Martin,

I am currently thinking about modifying it, so it gets the "show subtree" yes/no from the node, currently being travered. This would be a feature I will need in the future.

BTW: Combine the 'max_level' with the 'show_levels' and you have what you want.

Phil

H-Works Agency

Monday 10 July 2006 9:38:55 am

How can i use your code as an extension because i don't modify ezp source files for future updates.

Martin

EZP is Great

Philipp Simon

Monday 10 July 2006 10:12:42 am

Sorry, currently I haven't build an extension around it. So if you want to use it, rename the class and build the extension on your own. I simply replace the treemenu function, until ez has rebuild this operator. (I hope they will do so in the near future)

Phil

Kristof Coomans

Friday 14 July 2006 11:06:23 am

Thanks Phil for the contribution!

If not done yet, can you post an enhancement request for this in the bug tracker? You can refer to this topic.

independent eZ Publish developer and service provider | http://blog.coomanskristof.be | http://ezpedia.org

Powered by eZ Publish™ CMS Open Source Web Content Management. Copyright © 1999-2014 eZ Systems AS (except where otherwise noted). All rights reserved.