Category tree

Mar 30, 2011 at 8:37 PM

Hi

I'm having a bit of problem with your otherwise wonderful SiteMap.

I need to model a data-driven hierarchy where the depth of the hierarchy isn't known at design time. How would I do that using the SiteMap?

 

It *appears* (to me at least) that the hierarchy is baked-in to the .sitemap file and only the items at each node are dynamic. Am I missing something?

 

Thanks in advance for any suggestions :D

 

 

Lee.

Mar 31, 2011 at 4:22 PM

I'm not sure I explained that as well as I could have.

Imagine a category-hierarchy, as follows:

+-------------+----------------------+--------+
| category_id | name                 | parent |
+-------------+----------------------+--------+        ELECTRONICS
|           1 | ELECTRONICS          |   NULL |         + TELEVISIONS
|           2 | TELEVISIONS          |      1 |         |  + TUBE
|           3 | TUBE                 |      2 |         |  + LCD  
|           4 | LCD                  |      2 |         |  + PLASMA
|           5 | PLASMA               |      2 |         + PORTABLE ELECTRONICS
|           6 | PORTABLE ELECTRONICS |      1 |            + MP3 PLAYERS
|           7 | MP3 PLAYERS          |      6 |            | + FLASH
|           8 | FLASH                |      7 |            + CD PLAYERS
|           9 | CD PLAYERS           |      6 |            + 2 WAY RADIOS
|          10 | 2 WAY RADIOS         |      6 |
+-------------+----------------------+--------+

When navigating to mySite.com/store/category/8

I'd like the SiteMap to show: Store > ELECTRONICS > PORTABLE ELECTRONICS > MP3 PLAYERS > FLASH

Does anyone know if this (or something similar) is possible?

 

Thanks :D

 

 

Lee.

Mar 31, 2011 at 7:31 PM

You'll need DynamicNodeProviders.   Basically, you can setup a view in your database to return all nodes of a specific depth in the tree, with their respective parentID.
Each batch of nodes at a certain depth will be a NodeProvider. 

Dealing with Arbitrary depths however, I don't know.  I never figured it out myself.

Mar 31, 2011 at 7:40 PM
Edited Mar 31, 2011 at 7:43 PM

Thanks Tom :-)

 

It's really the arbitrary-depths thing that's causing me the problem, as the electronics hierarchy (in this example, stolen from http://dev.mysql.com/tech-resources/articles/hierarchical-data.html to save time, isn't known at design-time.)

On the one hand I can't imagine that the MvcSiteMapProvider assumes a fixed hierarchy with (where appropriate) variable child-nodes via a DynamicNodeProvider but in the absence of more documentation, that's what it looks like. Still, part of my mind doesn't want to believe it, despite that you appear to be saying it :

 * Each batch of nodes at a certain depth will be a NodeProvider.

 

Thanks anyway...

 

 

:D

Apr 1, 2011 at 2:04 PM

Hi Easy,

The only way I've been able to get arbitrary depth to work is when I know the maximum depth.  I can get certain branches to display properly if they have less than the max depth, but I always seem to have to travel down
to the deepest leaf to get things working properly.

For the arbitrary depth, how are you thinking of handling things in the Controllers / Views?  Would you pass a collection to a Controller instead of a single parameter?

Tom 

Apr 1, 2011 at 2:33 PM
Edited Apr 1, 2011 at 2:37 PM

Hi Tom

 

The controllers just take the id of the category - they're free to gather whatever extra information they need from the injected services.

The views get different information depending on what they need to display. Generally, a view also doesn't care where in the hierarchy the category is, it's just concerned with manipulating properties or dependent/associated objects.

 

It would be nice if there were an easier way of specifying nodes - particularly for edge-cases like this where there is a self-join situation in-play. For example, a property of an MvcSiteMapNode in the .sitemap file whose presence indicates that:

 * The MvcSiteMapNode is the root of a tree

 * The DynamicNodeProvider specified at that node will provide all levels of that tree rather than just one level.

 * If the ParentKey of a DynamicNode provided by the DynamicNodeProvider specifies another DynamicNode's Key, the MvcSiteMap will essentially promote that DynamicNode to an MvcSiteMapNode as if it were hard-wired in the .sitemap file

 

I can't tell from the documentation, or source code, whether this is possible.

 

Anyway, thanks for your time :D

 

Lee.

 

 

 

 

Apr 5, 2011 at 7:29 AM

This is currently possible, however it will require some hacking here and there. How would you like to see this implemented? Since the depth is not known in the sitemap, how would the sitemap provider get the info it requires, preferrably dynamically without having to have nodes for all products in memory?

Apr 5, 2011 at 2:56 PM
Edited Apr 5, 2011 at 3:06 PM
Hi Maarten - thanks for your reply.


I'll refer to the previous example:


-----------------------------------------------------------------------------------------------------------------
(1 adjacency-list representation from database)        (2 tree representation)
+-------------+----------------------+--------+
| category_id | name                 | parent |
+-------------+----------------------+--------+        ELECTRONICS
|           1 | ELECTRONICS          |   NULL |         + TELEVISIONS
|           2 | TELEVISIONS          |      1 |         |  + TUBE
|           3 | TUBE                 |      2 |         |  + LCD  
|           4 | LCD                  |      2 |         |  + PLASMA
|           5 | PLASMA               |      2 |         + PORTABLE ELECTRONICS
|           6 | PORTABLE ELECTRONICS |      1 |            + MP3 PLAYERS
|           7 | MP3 PLAYERS          |      6 |            | + FLASH
|           8 | FLASH                |      7 |            + CD PLAYERS
|           9 | CD PLAYERS           |      6 |            + 2 WAY RADIOS
|          10 | 2 WAY RADIOS         |      6 |
+-------------+----------------------+--------+


When navigating to mySite.com/store/category/8

I'd like the SiteMap to show: Store > ELECTRONICS > PORTABLE ELECTRONICS > MP3 PLAYERS > FLASH
-----------------------------------------------------------------------------------------------------------------



The way I imagine this working is that there might be the following snippet of .sitemap file:


<mvcSiteMapNode
  title="Store"
  controller="Store" 
  action="Store" 
  key="StoreKey" 
>
    <mvcSiteMapNode 
      title="Store category" 
      controller="Store" 
      action="Category" 
      dynamicNodeProvider="Namespace.CategoryDynamicNodeProvider, AssemblyName"
    >
        <mvcSiteMapNode 
          title="Store Category Item List" 
          action="List" 
          dynamicNodeProvider="Namespace.CategoryContentsDynamicNodeProvider, AssemblyName"
        />
    </mvcSiteMapNode>
</mvcSiteMapNode>


The mvcSiteMapNode for 'Store category' has a DynamicNodeProvider which basically returns the adjacency list from (1) above:


    public class CategoryDynamicNodeProvider : DynamicNodeProviderBase
    {
        IProviderCategoryRepository CategoryRepository { get; set; }

        public CategoryDynamicNodeProvider (IProviderCategoryRepository categoryRepository) {
            this.CategoryRepository = categoryRepository;
        }
        
        public override IEnumerable<DynamicNode> GetDynamicNodeCollection() {

            var categories = (from cat in CategoryRepository.All()
              select new DynamicNode {
                Title       = cat.Name,
                Key         = "Category_" + cat.ID,
                ParentKey   = cat.ParentID == null ? "StoreKey" : ("Category_" + cat.ParentID),
                RouteValues = new Dictionary<string, object> () {{ "id", cat.ID }}
              }).ToList ();

            return categories;
        }
    }

Each DynamicNode returned, specifies a parent via ParentKey which is either 'StoreKey' for top-level nodes or the Key of another node from the same collection. In the example above, each node uses the same action '/store/category/id' although presumably each node could specify route-values, as-needed, to generate other URLs.

When generating the path to display in the site-map, used within views, presumably, all that's needed is a walk up the hierarchy from the current category-node, through the dynamically-created nodes, until reaching a 'fixed' (i.e. specified within the .sitemap file) node and then standard behavior from there...

From my perspective of not understanding the inner-workings of the existing code, this seems fairly similar to the existing behavior other than the ability to have dynamically-created nodes specify other dynamically-created nodes as parents.

Does this make sense?

Kind regards,
Lee.

Apr 7, 2011 at 12:40 PM
Edited Apr 7, 2011 at 12:41 PM

*bump*

Apr 20, 2011 at 3:56 PM

Just thinking about a clean solution to this as it would re-expose a bug if I take the approach you suggested...