MOSS 2007中“快速启动”菜单的显示行为不一致

时间:2008-09-18 16:27:30

标签: sharepoint moss

我正在尝试将“快速启动”菜单配置为仅显示当前所选节点的祖先和后代节点。菜单还需要显示根节点的所有childern。更简单:

给出一个站点地图:

RootSite

--- SubSite1 =导航设置为“显示当前站点,当前站点下方的导航项目以及当前站点的兄弟姐妹”

----- 标题1 =导航设置为“显示与父网站相同的导航项目”

------- Page1 =导航设置为“显示与父网站相同的导航项目”

------- Page2 =导航设置为“显示与父网站相同的导航项目”

----- 标题2 =导航设置为“显示与父网站相同的导航项目”

--- SubSite2 =导航设置为“显示当前站点,当前站点下方的导航项目以及当前站点的兄弟姐妹”

----- 标题1 =导航设置为“显示与父网站相同的导航项目”

SiteMapProvider配置:

<PublishingNavigation:PortalSiteMapDataSource ID="SiteMapDS" Runat="server"
SiteMapProvider="CurrentNavSiteMapProvider" EnableViewState="true"
StartFromCurrentNode="true" ShowStartingNode="false"/>

SubSite1上显示的“快速启动”菜单的预期和实际行为是:

--- subsite1的

-----标题1

-------第1页

------- 2页

----- Heading2

--- SubSite2

导航到SubSite2的Heading1后菜单的预期行为:

--- subsite1的

--- SubSite2

-----标题1

导航到SubSite2的Heading1后我实际看到的内容:

--- subsite1的

-----标题1

-------第1页

------- 2页

----- Heading2

--- SubSite2

-----标题1

如果我将Heading1导航设置为“显示”,那么这与我期望看到的不符 与父网站相同的导航项目“和SubSite2设置为”显示当前网站,当前网站下方的导航项目以及当前网站的兄弟姐妹“。我希望 Heading1继承SubSite2的导航项,SubSite1项从视图中折叠。我也玩过各种各样的 修剪...属性没有成功。任何帮助将不胜感激!

3 个答案:

答案 0 :(得分:2)

我按照@ Nat的指导进入了阴暗的世界Sharepoint webparts,以实现我上面描述的行为。我的方法是推出我自己的微软通过ECM团队博客发布的MossMenu webpart版本。此代码基于本机AspMenu控件。我使用此控件来“拦截”通过标记中的DataSourceId属性注入的本机SiteMapDataSource,并创建一个新的XML数据源以展示所需的行为。我在这个罗嗦的答案结尾处加入了最终的源代码。以下是母版页标记中的位:

<%@ Register TagPrefix="myCustom" Namespace="YourCompany.CustomWebParts"
   Assembly="YourCompany.CustomWebParts, Version=1.0.0.0, Culture=neutral,
   PublicKeyToken=9f4da00116c38ec5" %>

...

<myCustom:MossMenu ID="CurrentNav" runat="server" datasourceID="SiteMapDS"
    orientation="Vertical" UseCompactMenus="true" StaticDisplayLevels="6"
    MaximumDynamicDisplayLevels="0" StaticSubMenuIndent="5" ItemWrap="false"
    AccessKey="3" CssClass="leftNav"
    SkipLinkText="<%$Resources:cms,masterpages_skiplinktext%>">
    <LevelMenuItemStyles>
        <asp:MenuItemStyle CssClass="Nav" />
        <asp:MenuItemStyle CssClass="SecNav" />
    </LevelMenuItemStyles>
    <StaticHoverStyle CssClass="leftNavHover"/>
    <StaticSelectedStyle CssClass="leftNavSelected"/>
    <DynamicMenuStyle CssClass="leftNavFlyOuts" />
    <DynamicMenuItemStyle CssClass="leftNavFlyOutsItem"/>
    <DynamicHoverStyle CssClass="leftNavFlyOutsHover"/>
</myCustom:MossMenu>

<PublishingNavigation:PortalSiteMapDataSource ID="SiteMapDS" Runat="server"
     SiteMapProvider="CurrentNavSiteMapProvider" EnableViewState="true"
     StartFromCurrentNode="true" ShowStartingNode="false"/>

...

我按照优秀的分步说明在MossMenu webpart的评论部分创建我的自定义网络部分,“2007年9月19日星期三上午7:20由Roel”。在我的谷歌搜索中,我还找到了一些东西来配置一个Sharepoint站点,以通过使web.config更改here以与ASP.NET相同的方式显示异常。{/ p>

我决定将自定义行为称为“紧凑菜单”,因此我在控件上创建了一个UseCompactMenus属性。如果未将标记中的此属性设置为true,则控件的行为与AspMenu控件的行为相同。

我的应用程序让用户始终从站点地图根目录的主页开始。当显示根页面时,我可以让自定义控件存储初始(完整)站点地图。它存储在静态字符串中,以用于自定义行为。如果您的应用程序不遵循此假设,则控件将无法按预期工作。

在初始应用程序页面上,菜单中仅显示指向根页面的直接子页面。单击这些菜单节点将打开其下的所有子节点,但保持兄弟节点“关闭”。如果单击其中一个兄弟节点,它将折叠当前节点并打开新选择的节点。就是这样,享受!!

using System;
using System.Text;
using System.ComponentModel;
using System.Collections.Generic;
using System.Security.Permissions;
using System.Xml;
using System.Xml.Serialization;

using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design.WebControls;

using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Security;

namespace YourCompany.CustomWebParts
{
    [AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
    [SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true)]
    [Designer(typeof(MossMenuDesigner))]
    [ToolboxData("<{0}:MossMenu runat=\"server\" />")]
    public class MossMenu : System.Web.UI.WebControls.Menu
    {
        private string idPrefix;

        // a url->menuItem dictionary
        private Dictionary<string, System.Web.UI.WebControls.MenuItem> menuItemDictionary =
            new Dictionary<string, System.Web.UI.WebControls.MenuItem>(StringComparer.OrdinalIgnoreCase);

        private bool customSelectionEnabled = true;
        private bool selectStaticItemsOnly = true;

        private bool performTargetBinding = true;

        //** Variables used for compact menu behavior **//
        private bool useCompactMenus = false;
        private static bool showStartingNode;
        private static string originalSiteMap;

        /// <summary>
        /// Controls whether or not the control performs compacting of the site map to display only ancestor and child nodes of the selected and first level root childern.
        /// </summary>
        [Category("Behavior")]
        public bool UseCompactMenus
        {
            get
            {
                return this.useCompactMenus;
            }
            set
            {
                this.useCompactMenus = value;
            }
        }

        /// <summary>
        /// Controls whether or not the control performs custom selection/highlighting.
        /// </summary>
        [Category("Behavior")]
        public bool CustomSelectionEnabled
        {
            get
            {
                return this.customSelectionEnabled;
            }
            set
            {
                this.customSelectionEnabled = value;
            }
        }

        /// <summary>
        /// Controls whether only static items may be selected or if
        /// dynamic (fly-out) items may be selected too.
        /// </summary>
        [Category("Behavior")]
        public bool SelectStaticItemsOnly
        {
            get
            {
                return this.selectStaticItemsOnly;
            }
            set
            {
                this.selectStaticItemsOnly = value;
            }
        }

        /// <summary>
        /// Controls whether or not to bind the Target property of any menu
        /// items to the Target property in the SiteMapNode's Attributes
        /// collection.
        /// </summary>
        [Category("Behavior")]
        public bool PerformTargetBinding
        {
            get
            {
                return this.performTargetBinding;
            }
            set
            {
                this.performTargetBinding = value;
            }
        }

        /// <summary>
        /// Gets the ClientID of this control. 
        /// </summary>
        public override string ClientID
        {
            [SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
            get
            {
                if (this.idPrefix == null)
                {
                    this.idPrefix = SPUtility.GetNewIdPrefix(this.Context);
                }

                return SPUtility.GetShortId(this.idPrefix, this);
            }
        }

        [SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
        protected override void OnMenuItemDataBound(MenuEventArgs e)
        {
            base.OnMenuItemDataBound(e);

            if (this.customSelectionEnabled)
            {
                // store in the url->item dictionary
                this.menuItemDictionary[e.Item.NavigateUrl] = e.Item;
            }

            if (this.performTargetBinding)
            {
                // try to bind to the Target property if the data item is a SiteMapNode
                SiteMapNode smn = e.Item.DataItem as SiteMapNode;
                if (smn != null)
                {
                    string target = smn["Target"];
                    if (!string.IsNullOrEmpty(target))
                    {
                        e.Item.Target = target;
                    }
                }
            }
        }

        /// <id guid="08e034e7-5872-4a31-a771-84cac1dcd53d" />
        /// <owner alias="MarkWal">
        /// </owner>
        [SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
        protected override void OnPreRender(System.EventArgs e)
        {

            SiteMapDataSource dataSource = this.GetDataSource() as SiteMapDataSource;
            SiteMapProvider provider = (dataSource != null) ? dataSource.Provider : null;

            if (useCompactMenus && dataSource != null && provider != null)
            {
                showStartingNode = dataSource.ShowStartingNode;

                SiteMapNodeCollection rootChildNodes = provider.RootNode.ChildNodes;

                if (provider.CurrentNode.Equals(provider.RootNode))
                {
                    //** Store original site map for future use in compacting menus **//
                    if (originalSiteMap == null)
                    {
                        //Store original SiteMapXML for future adjustments:
                        XmlDocument newSiteMapDoc = new XmlDocument();
                        newSiteMapDoc.LoadXml("<?xml version='1.0' ?>"
                            + "<siteMapNode title='" + provider.RootNode.Title
                            + "' url='" + provider.RootNode.Url
                            + "' />");

                        foreach (SiteMapNode node in rootChildNodes)
                        {
                            XmlNode newNode = GetXmlSiteMapNode(newSiteMapDoc.DocumentElement, node);

                            newSiteMapDoc.DocumentElement.AppendChild(newNode);

                            //Create XML for all the child nodes for selected menu item:
                            NavigateSiteMap(newNode, node);
                        }

                        originalSiteMap = newSiteMapDoc.OuterXml;
                    }

                    //This is set to only display the child nodes of the root node on first view:
                    this.StaticDisplayLevels = 1;
                }
                else
                {
                    //
                    //Adjust site map for this page
                    //
                    XmlDocument newSiteMapDoc = InitializeNewSiteMapXml(provider, rootChildNodes);

                    //Clear the current default site map:
                    this.DataSourceID = null;

                    //Create the new site map data source
                    XmlDataSource newSiteMap = new XmlDataSource();
                    newSiteMap.ID = "XmlDataSource1";
                    newSiteMap.EnableCaching = false; //Required to prevent redisplay of the previous menu

                    //Add bindings for dynamic site map:
                    MenuItemBindingCollection bindings = this.DataBindings;
                    bindings.Clear();

                    MenuItemBinding binding = new MenuItemBinding();
                    binding.DataMember = "siteMapNode";
                    binding.TextField = "title";
                    binding.Text = "title";
                    binding.NavigateUrlField = "url";
                    binding.NavigateUrl = "url";
                    binding.ValueField = "url";
                    binding.Value = "url";

                    bindings.Add(binding);

                    //Bind menu to new site map:
                    this.DataSource = newSiteMap;

                    //Assign the newly created dynamic site map:
                    ((XmlDataSource)this.DataSource).Data = newSiteMapDoc.OuterXml;

                    /** this expression removes the root if initialized: **/
                    if (!showStartingNode)
                        ((XmlDataSource)this.DataSource).XPath = "/siteMapNode/siteMapNode";

                    /** Re-initialize menu data source with new site map: **/
                    this.DataBind();

                    /** Find depth of current node: **/
                    int depth = 0;
                    SiteMapNode currNode = provider.CurrentNode;
                    do
                    {
                        depth++;
                        currNode = currNode.ParentNode;
                    }
                    while (currNode != null);

                    //Set the StaticDisplayLevels to match the current depth:
                    if (depth >= this.StaticDisplayLevels)
                        this.StaticDisplayLevels = depth;
                }
            }

            base.OnPreRender(e);

            // output some script to override the default menu flyout behaviour; this helps to avoid
            // intermittent "Operation Aborted" errors
            Page.ClientScript.RegisterStartupScript(
                typeof(MossMenu),
                "overrideMenu_HoverStatic",
                "if (typeof(overrideMenu_HoverStatic) == 'function' && typeof(Menu_HoverStatic) == 'function')\n" +
                "{\n" +
                    "_spBodyOnLoadFunctionNames.push('enableFlyoutsAfterDelay');\n" +
                    "Menu_HoverStatic = overrideMenu_HoverStatic;\n" +
                "}\n",
                true);

            // output some script to avoid a known issue with SSL Termination and the ASP.NET
            // Menu implementation. http://support.microsoft.com/?id=910444
            Page.ClientScript.RegisterStartupScript(
                typeof(MossMenu),
                "MenuHttpsWorkaround_" + this.ClientID,
                this.ClientID + "_Data.iframeUrl='/_layouts/images/blank.gif';",
                true);

            // adjust the fly-out indicator arrow direction for locale if not already set
            if (this.Orientation == System.Web.UI.WebControls.Orientation.Vertical &&
                ((string.IsNullOrEmpty(this.StaticPopOutImageUrl) && this.StaticEnableDefaultPopOutImage) ||
                    (string.IsNullOrEmpty(this.DynamicPopOutImageUrl) && this.DynamicEnableDefaultPopOutImage)))
            {
                SPWeb currentWeb = SPContext.Current.Web;
                if (currentWeb != null)
                {
                    uint localeId = currentWeb.Language;

                    bool isBidiWeb = SPUtility.IsRightToLeft(currentWeb, currentWeb.Language);

                    string arrowUrl = "/_layouts/images/" + (isBidiWeb ? "largearrowleft.gif" : "largearrowright.gif");

                    if (string.IsNullOrEmpty(this.StaticPopOutImageUrl) && this.StaticEnableDefaultPopOutImage)
                    {
                        this.StaticPopOutImageUrl = arrowUrl;
                    }
                    if (string.IsNullOrEmpty(this.DynamicPopOutImageUrl) && this.DynamicEnableDefaultPopOutImage)
                    {
                        this.DynamicPopOutImageUrl = arrowUrl;
                    }
                }
            }

            if (provider == null)
            {
                // if we're not attached to a SiteMapDataSource we'll just leave everything alone
                return;
            }
            else if (this.customSelectionEnabled)
            {
                MenuItem selectedMenuItem = this.SelectedItem;
                SiteMapNode currentNode = provider.CurrentNode;

                // if no menu item is presently selected, we need to work our way up from the current 
                // node until we can find a node in the menu item dictionary
                while (selectedMenuItem == null && currentNode != null)
                {
                    this.menuItemDictionary.TryGetValue(currentNode.Url, out selectedMenuItem);

                    currentNode = currentNode.ParentNode;
                }

                if (this.selectStaticItemsOnly)
                {
                    // only static items may be selected, keep moving up until we find an item
                    // that falls within the static range
                    while (selectedMenuItem != null && selectedMenuItem.Depth >= this.StaticDisplayLevels)
                    {
                        selectedMenuItem = selectedMenuItem.Parent;
                    }

                    // if we found an item to select, go ahead and select (highlight) it
                    if (selectedMenuItem != null && selectedMenuItem.Selectable)
                    {
                        selectedMenuItem.Selected = true;
                    }
                }
            }
        }

        private XmlDocument InitializeNewSiteMapXml(SiteMapProvider provider, SiteMapNodeCollection rootChildNodes)
        {
            /** Find the level 1 ancestor node of the current node: **/
            SiteMapNode levelOneAncestorOfSelectedNode = null;
            SiteMapNode currNode = provider.CurrentNode;
            do
            {
                levelOneAncestorOfSelectedNode = (currNode.ParentNode == null ? levelOneAncestorOfSelectedNode : currNode);
                currNode = currNode.ParentNode;
            }
            while (currNode != null);

            /** Initialize base SiteMapXML **/
            XmlDocument newSiteMapDoc = new XmlDocument();
            newSiteMapDoc.LoadXml(originalSiteMap);

            /** Prune out the childern nodes that shouldn't display: **/
            currNode = provider.CurrentNode;
            do
            {
                if (currNode.ParentNode != null)
                {
                    SiteMapNodeCollection currNodeSiblings = currNode.ParentNode.ChildNodes;
                    foreach (SiteMapNode siblingNode in currNodeSiblings)
                    {
                        if (siblingNode.HasChildNodes)
                        {
                            if (provider.CurrentNode.Equals(siblingNode))
                            {
                                //Remove all the childerns child nodes from display:
                                SiteMapNodeCollection currNodesChildren = siblingNode.ChildNodes;
                                foreach (SiteMapNode childNode in currNodesChildren)
                                {
                                    XmlNode currentXmNode = GetCurrentXmlNode(newSiteMapDoc, childNode);

                                    DeleteChildNodes(currentXmNode);
                                }
                            }
                            else if (!provider.CurrentNode.IsDescendantOf(siblingNode)
                                && !levelOneAncestorOfSelectedNode.Equals(siblingNode))
                            {
                                XmlNode currentXmNode = GetCurrentXmlNode(newSiteMapDoc, siblingNode);

                                DeleteChildNodes(currentXmNode);
                            }
                        }
                    }
                }

                currNode = currNode.ParentNode;
            }
            while (currNode != null);

            return newSiteMapDoc;
        }

        private XmlNode GetCurrentXmlNode(XmlDocument newSiteMapDoc, SiteMapNode node)
        {
            //Find this node in the original site map:
            XmlNode currentXmNode = newSiteMapDoc.DocumentElement.SelectSingleNode(
                "//siteMapNode[@url='"
                + node.Url
                + "']");
            return currentXmNode;
        }

        private void DeleteChildNodes(XmlNode currentXmNode)
        {
            if (currentXmNode != null && currentXmNode.HasChildNodes)
            {
                //Remove child nodes:
                XmlNodeList xmlNodes = currentXmNode.ChildNodes;
                int lastNodeIndex = xmlNodes.Count - 1;
                for (int i = lastNodeIndex; i >= 0; i--)
                {
                    currentXmNode.RemoveChild(xmlNodes[i]);
                }
            }
        }
        private XmlNode GetXmlSiteMapNode(XmlNode currentDocumentNode, SiteMapNode currentNode)
        {
            XmlElement newNode = currentDocumentNode.OwnerDocument.CreateElement("siteMapNode");

            XmlAttribute newAttr = currentDocumentNode.OwnerDocument.CreateAttribute("title");
            newAttr.InnerText = currentNode.Title;
            newNode.Attributes.Append(newAttr);

            newAttr = currentDocumentNode.OwnerDocument.CreateAttribute("url");
            newAttr.InnerText = currentNode.Url;
            newNode.Attributes.Append(newAttr);

            return newNode;
        }

        private void NavigateSiteMap(XmlNode currentDocumentNode, SiteMapNode currentNode)
        {
            foreach (SiteMapNode node in currentNode.ChildNodes)
            {
                //Add this node to structure:
                XmlNode newNode = GetXmlSiteMapNode(currentDocumentNode, node);
                currentDocumentNode.AppendChild(newNode);

                if (node.HasChildNodes)
                {
                    //Make a recursive call to add any child nodes:
                    NavigateSiteMap(newNode, node);
                }
            }
        }
    }

    [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2117:AptcaTypesShouldOnlyExtendAptcaBaseTypes")]
    public sealed class MossMenuDesigner : MenuDesigner
    {
        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
        protected override void DataBind(BaseDataBoundControl dataBoundControl)
        {
            try
            {
                dataBoundControl.DataBind();
            }
            catch
            {
                base.DataBind(dataBoundControl);
            }
        }

        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
        public override string GetDesignTimeHtml()
        {
            System.Web.UI.WebControls.Menu menu = (System.Web.UI.WebControls.Menu)ViewControl;
            int oldDisplayLevels = menu.MaximumDynamicDisplayLevels;
            string designTimeHtml = string.Empty;

            try
            {
                menu.MaximumDynamicDisplayLevels = 0;

                // ASP.NET MenuDesigner has some dynamic/static item trick in design time
                // to show dynamic item in design time. We only want to show preview without
                // dynamic menu items.
                designTimeHtml = base.GetDesignTimeHtml();
            }
            catch (Exception e)
            {
                designTimeHtml = GetErrorDesignTimeHtml(e);
            }
            finally
            {
                menu.MaximumDynamicDisplayLevels = oldDisplayLevels;
            }

            return designTimeHtml;
        }
    }
}

答案 1 :(得分:1)

我个人不喜欢默认菜单提供的html(基于表格的布局)。 幸运的是,SharePoint团队已经发布了control的代码。

我们所做的是将该代码包含在项目中并覆盖render方法以执行我们想要的任何操作。这使您可以灵活地定义需要显示的父项之间的确切关系,以及在您创建的任何div上设置样式。

在缺点方面,您现在正在编码,而不是进行配置,并且需要对用于使用控件的母版页进行更改。

值得我认为。现在,这是我们为任何网站所做的标准更改。

答案 2 :(得分:0)

我们用来实现您正在寻找的影响的方法是使用CSS Friendly Control Adapters。适配器更改呈现的HTML,而不更改您在页面上使用的控件。您可能需要稍微调整菜单适配器以获得所需的布局。它只为我们花了几行代码。一旦你开始工作,就可以使用CSS来获得你描述的行为。