Dynamic header menu

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
14 anos atrás
So the header menu is great but it lacks the depth in the event you want your site to be based wholly on a SingleColumn layout (no left or right column navigation). I have been through the breadcrumb code and think it makes a fine menu but to make a really clean flexible menu, I dont think its quite adequate.

One could add a table that follows the SQLSiteMap guidelines but that would be difficult to integrate with the current category management (in the event you wanted to move all the shopping links to the header via dropdowns).

I have started this on my site:
http://sandbox.elementalcoffeeroasters.com

But it is really a kludge as the Shop and Info dropdowns are static from a static XmlSiteMap I added. I have been scouring around looking for something that will accomplish pulling dynamic links in from various sources but there doesn't appear to be anything. At this point, I am thinking a dynamically generated XmlSiteMap might be the best way.

Does anyone know of any exisiting project that builds the sitemap files? I'm still looking but am running out of hope of finding something prebuilt.

Thanks,

C.
14 anos atrás
I found this other thread.

https://www.nopcommerce.com/Boards/Topic.aspx?TopicID=1519

Help?

I'm not too "up" on the sitemap files. Just saw that from a google search is all.
14 anos atrás
That generates an xml file that google can read for spidering your site (and other things).

What I am looking for is something to make an asp.net XmlSiteMapProvider sitemap dynamic. I actually think I have located what I need... will post back when I get it working.

C.
14 anos atrás
For those of you who might be interested in something like this...

Essentially what I did was hack a bit on this guy's http://tinyurl.com/2t4olk idea. So the steps would be:

- Create a web.sitemap
- Add sitemap to your web.config
- Create a HeaderMenuNew.ascx control
- Reference your sitemap and add your menu control (horizontal... etc)
- Go to the Root.Master and change the HeaderMenu reference to use your new header control
- Hack CSS until it looks the way you want
- Now for the dynamic stuff
- Use the above link to create a new SiteMapProvider that does the dynamic stuff you need it to do.
- Hack the URL rewriting stuff so it keeps the SEO url's (hint, do this in the OnMenuItemDataBound event on your menu user control)

And that does it. Let me know if you have any questions.

C.
14 anos atrás
I'll have to try this out. Thanks for the pointers.
14 anos atrás
Here is the Dynamic sitemap class modified from its original state. I put this in the Business logic namespace under Utils. Notice that the stuff that is relevant for a nopCommerce category menu is in the AddDynamicNodes() method:


/*  
* DynamicSiteMapProvider
*
* Created by Simon Harriyott http://harriyott.com
* More details at http://tinyurl.com/2t4olk
*
* DynamicSiteMapProvider is a class that adds new
* items to a site map at runtime. The filename of
* the existing xml sitemap is set in the Web.Config
* file. This file is loaded, and extra nodes can
* be added to the xml (by you) before the new xml is
* converted into a site map node structure, and passed
* back to the calling object.
*
* Feel free to use and modify this class for personal,
* non-profit or commercial use, without charge. I would
* appreciate you leaving this comment here. I'd also
* appreciate a link to my blog from your website, but
* I'll leave that up to you.
*
*/

#region Using
using System;
using System.Web;
using System.Web.UI;
using System.Xml;
using NopSolutions.NopCommerce.BusinessLogic.Categories;
using NopSolutions.NopCommerce.BusinessLogic.Products;
using NopSolutions.NopCommerce.BusinessLogic.SEO;
#endregion


namespace NopSolutions.NopCommerce.BusinessLogic.Utils {
  /// <summary>
  /// Adds new items to an existing xml site map at runtime.
  /// Modify this class to add your own items.
  /// </summary>
  public class DynamicSiteMapProvider : StaticSiteMapProvider {
    public DynamicSiteMapProvider()
      : base() {

    }

    private String _siteMapFileName;
    private SiteMapNode _rootNode = null;
    private XmlNamespaceManager _NamespaceManager;

    public XmlNamespaceManager NamespaceManager {
      get { return _NamespaceManager; }
    }



    // Return the root node of the current site map.
    public override SiteMapNode RootNode {
      get {
        return BuildSiteMap();
      }
    }

    /// <summary>
    /// Pull out the filename of the site map xml.
    /// </summary>
    /// <param name="name"></param>
    /// <param name="attributes"></param>
    public override void Initialize(string name, System.Collections.Specialized.NameValueCollection attributes) {
      base.Initialize(name, attributes);
      _siteMapFileName = attributes["siteMapFile"];
    }

    private const String SiteMapNodeName = "siteMapNode";

    public override SiteMapNode BuildSiteMap() {
      lock (this) {
        if (null == _rootNode) {
          Clear();

          // Load the sitemap's xml from the file.
          XmlDocument siteMapXml = LoadSiteMapXml();

          _NamespaceManager = new XmlNamespaceManager(siteMapXml.NameTable);
          _NamespaceManager.AddNamespace("map", siteMapXml.DocumentElement.NamespaceURI);


          // Create the first site map item from the top node in the xml.
          XmlElement rootElement =
            (XmlElement)siteMapXml.GetElementsByTagName(
            SiteMapNodeName)[0];

          // This is the key method - add the dynamic nodes to the xml
          AddDynamicNodes(rootElement);

          // Now build up the site map structure from the xml
          GenerateSiteMapNodes(rootElement);
        }
      }
      return _rootNode;
    }

    /// <summary>
    /// Open the site map file as an xml document.
    /// </summary>
    /// <returns>The contents of the site map file.</returns>
    private XmlDocument LoadSiteMapXml() {
      XmlDocument siteMapXml = new XmlDocument();
      siteMapXml.Load(AppDomain.CurrentDomain.BaseDirectory + _siteMapFileName);
      return siteMapXml;
    }

    /// <summary>
    /// Creates the site map nodes from the root of
    /// the xml document.
    /// </summary>
    /// <param name="rootElement">The top-level sitemap element from the XmlDocument loaded with the site map xml.</param>
    private void GenerateSiteMapNodes(XmlElement rootElement) {
      _rootNode = GetSiteMapNodeFromElement(rootElement);
      AddNode(_rootNode);
      CreateChildNodes(rootElement, _rootNode);
    }

    /// <summary>
    /// Recursive method! This finds all the site map elements
    /// under the current element, and creates a SiteMapNode for
    /// them.  On each of these, it calls this method again to
    /// create it's new children, and so on.
    /// </summary>
    /// <param name="parentElement">The xml element to iterate through.</param>
    /// <param name="parentNode">The site map node to add the new children to.</param>
    private void CreateChildNodes(XmlElement parentElement, SiteMapNode parentNode) {
      foreach (XmlNode xmlElement in parentElement.ChildNodes) {
        if (xmlElement.Name == SiteMapNodeName) {
          SiteMapNode childNode = GetSiteMapNodeFromElement((XmlElement)xmlElement);
          AddNode(childNode, parentNode);
          CreateChildNodes((XmlElement)xmlElement, childNode);
        }
      }
    }


    /// <summary>
    /// The key method. You can add your own code in here
    /// to add xml nodes to the structure, from a
    /// database, file on disk, or just from code.
    /// To keep the example short, I'm just adding from code.
    /// </summary>
    /// <param name="rootElement"></param>
    private void AddDynamicNodes(XmlElement rootElement) {
      //This is the place to start adding dynamic content.
      //Get an instance of the top level menu item ... in this case "Shop"
      //and pass it into the BuildCategorySubMen method
      XmlElement shopElement = GetElementByTitle(rootElement, "Shop");
      if (shopElement != null) {

        BuildCategorySubMenu(shopElement);
      }
    }

    private static XmlElement AddDynamicChildElement(XmlElement parentElement, String url, String title, String description) {
      // Create new element from the parameters
      XmlElement childElement = parentElement.OwnerDocument.CreateElement(SiteMapNodeName);
      childElement.SetAttribute("url", url);
      childElement.SetAttribute("title", title);
      childElement.SetAttribute("description", description);

      // Add it to the parent
      parentElement.AppendChild(childElement);
      return childElement;
    }

    private SiteMapNode GetSiteMapNodeFromElement(XmlElement rootElement) {
      SiteMapNode newSiteMapNode;
      String url = rootElement.GetAttribute("url");
      String title = rootElement.GetAttribute("title");
      String description = rootElement.GetAttribute("description");

      // The key needs to be unique, so hash the url and title.
      newSiteMapNode = new SiteMapNode(this,
        (url + title).GetHashCode().ToString(), url, title, description);

      return newSiteMapNode;
    }

    protected void BuildCategorySubMenu(XmlElement topItem) {
      CategoryCollection breadcrumbs = CategoryManager.GetAllCategories(0);
      foreach (Category category in breadcrumbs) {
        if (CategoryManager.GetBreadCrumb(category.CategoryID).Count > 0) {
          XmlElement newTop = AddDynamicChildElement(topItem, string.Format("~/Category.aspx?CategoryID={0}", category.CategoryID), category.Name, category.Description);
          BuildSubMenu(CategoryManager.GetBreadCrumb(category.CategoryID), category.CategoryID, category, 0, newTop);
        }
        else {
          AddDynamicChildElement(topItem, string.Format("~/Category.aspx?CategoryID={0}",category.CategoryID), category.Name, category.Description);
        }
      }
    }

    protected void BuildSubMenu(CategoryCollection breadCrumb, int rootCategoryID,
                                 Category currentCategory1, int level, XmlElement topItem) {
      foreach (Category category in CategoryManager.GetAllCategories(rootCategoryID)) {
        if (breadCrumb.Count > 1) {
          XmlElement newTop = AddDynamicChildElement(topItem, string.Format("~/Category.aspx?CategoryID={0}", category.CategoryID), category.Name, category.Description);
          BuildSubMenu(CategoryManager.GetBreadCrumb(category.CategoryID), category.CategoryID, category, 0, newTop);
        }
        else {
          AddDynamicChildElement(topItem, string.Format("~/Category.aspx?CategoryID={0}", category.CategoryID), category.Name, category.Description);
        }
      }
    }

    private XmlElement GetElementByUrl(XmlElement rootElement, string url) {
      return rootElement.SelectSingleNode(String.Format("//map:{0}[@url='{1}']", SiteMapNodeName, url), NamespaceManager) as XmlElement;
    }

    //This could break if there are more than one elements with the same title
    //URL is a key for this node so it wont break
    private XmlElement GetElementByTitle(XmlElement rootElement, string title) {
      return rootElement.SelectSingleNode(String.Format("//map:{0}[@title='{1}']", SiteMapNodeName, title), NamespaceManager) as XmlElement;
    }


    protected override SiteMapNode GetRootNodeCore() {
      return RootNode;
    }

    // Empty out the existing items.
    protected override void Clear() {
      lock (this) {
        _rootNode = null;
        base.Clear();
      }
    }
  }
}
14 anos atrás
The problem you will have with the above code is that the SiteMapNode will not find the URL that is being rewritten by the UrlRewriter. Thus the writing of the physical URL. This is then changed back in the user control with the following event handler:

protected void menu1_DataBound(object sender, MenuEventArgs e) {
      if (e.Item.NavigateUrl.Contains("CategoryID")) {
        int CategoryId = int.Parse(e.Item.NavigateUrl.Split(new Char[] { '=' })[1]);
                
        Category category = CategoryManager.GetCategoryByID(CategoryId);
        if (category != null) {
          e.Item.NavigateUrl = SEOHelper.GetCategoryURL(category);
        }
      }
    }

This then rewrites the physical URL back to the search engine friendly URL.

I hope this helps...
14 anos atrás
Duplicate post deleted. Sorry.
14 anos atrás
I realized... after adding a few more menu items, that the code was buggy. If you are going to use this, replace the two methods below with the code here:

BuildCategorySubMenu()
BuildSubMenu()



    protected void BuildCategorySubMenu(XmlElement topItem) {
      CategoryCollection breadcrumbs = CategoryManager.GetAllCategories(0);
      foreach (Category category in breadcrumbs) {
        if (CategoryManager.GetAllCategories(category.CategoryID).Count > 0) {
          XmlElement newTop = AddDynamicChildElement(topItem, string.Format("~/Category.aspx?CategoryID={0}", category.CategoryID), category.Name, category.Description);
          BuildSubMenu( CategoryManager.GetAllCategories( category.CategoryID ), newTop );
        }
        else {
          AddDynamicChildElement(topItem, string.Format("~/Category.aspx?CategoryID={0}", category.CategoryID), category.Name, category.Description);
        }
      }
    }

    protected void BuildSubMenu(CategoryCollection categories, XmlElement topItem) {
      foreach (Category category in categories) {
        if ( CategoryManager.GetAllCategories( category.CategoryID ).Count > 0 ) {
          XmlElement newTop = AddDynamicChildElement(topItem, string.Format("~/Category.aspx?CategoryID={0}", category.CategoryID), category.Name, category.Description);
          BuildSubMenu(CategoryManager.GetBreadCrumb(category.CategoryID), newTop);
        }
        else {
          AddDynamicChildElement(topItem, string.Format("~/Category.aspx?CategoryID={0}", category.CategoryID), category.Name, category.Description);
        }
      }
    }
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.