Непоследовательное поведение отображения меню быстрого запуска в MOSS 2007

StackOverflow https://stackoverflow.com/questions/94154

  •  01-07-2019
  •  | 
  •  

Вопрос

Я пытаюсь настроить меню быстрого запуска так, чтобы в нем отображались только узлы-предки и узлы-потомки выбранного в данный момент узла.Меню также должно отображать все дочерние элементы корневого узла.Более просто:

Дана карта сайта компании:

Место укоренения

---Заменитель1 = навигация установлена в положение "Отображать текущий сайт, элементы навигации под текущим сайтом и родственные элементы текущего сайта".

-----Заголовок 1 = навигация установлена в положение "Отображать те же элементы навигации, что и на родительском сайте".

-------Страница 1 = навигация установлена в положение "Отображать те же элементы навигации, что и на родительском сайте".

-------Страница 2 = навигация установлена в положение "Отображать те же элементы навигации, что и на родительском сайте".

-----Заголовок 2 = навигация установлена в положение "Отображать те же элементы навигации, что и на родительском сайте".

---Заменитель2 = навигация установлена в положение "Отображать текущий сайт, элементы навигации под текущим сайтом и родственные элементы текущего сайта".

-----Заголовок 1 = навигация установлена в положение "Отображать те же элементы навигации, что и на родительском сайте".

Конфигурация SiteMapProvider:

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

Ожидаемое и фактическое поведение меню быстрого запуска, отображаемого в подзаголовке 1, выглядит следующим образом:

---Подзаголовок 1

-----Заголовок 1

-------Страница 1

-------Страница 2

-----Заголовок 2

---Подстановка2

Ожидаемое поведение меню после перехода к заголовку1 подзаголовка2:

---Подзаголовок 1

---Подстановка2

-----Заголовок 1

Что я на самом деле вижу после перехода к Заголовку1 подзаголовка2:

---Подзаголовок 1

-----Заголовок 1

-------Страница 1

-------Страница 2

-----Заголовок 2

---Подстановка2

-----Заголовок 1

Это не соответствует тому, что я ожидаю увидеть, если я установлю для навигации по заголовку 1 значение "Отображать те же элементы навигации, что и на родительском сайте", а для SubSite2 установлено значение "Отображать текущий сайт, элементы навигации под текущим сайтом и дочерние элементы текущего сайта".Я ожидаю, что Heading1 унаследует элемент навигации SubSite2 с элементами SubSite1, свернутыми из вида.Я также играл с различными Отделка...атрибуты безуспешно.Любая помощь будет высоко оценена!

Это было полезно?

Решение

Я последовал указаниям @Nat в темном мире веб-частей Sharepoint, чтобы добиться поведения, описанного мной выше.Мой подход состоял в том, чтобы создать свою собственную версию Веб-страница MossMenu который Microsoft опубликовала в блоге ECM Team.Этот код основан на собственном элементе управления AspMenu.Я использовал этот элемент управления, чтобы "перехватить" собственный SiteMapDataSource, введенный через атрибут DataSourceID в разметке, и создать новый источник данных 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 в "Среда, 19 сентября 2007 г., 7:20 утра от Roel".В моем поиске в Google я также нашел кое-что, позволяющее настроить сайт Sharepoint для отображения исключений тем же прекрасным способом, что и ASP.NET, путем внесения изменений в web.config здесь.

Я решил назвать свое пользовательское поведение "компактным меню", поэтому я создал свойство 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;
        }
    }
}

Другие советы

Лично мне не нравится html, который предоставляет меню по умолчанию (табличный макет).К счастью, команда SharePoint выпустила код для этого контроль.

Что мы сделали, так это включили этот код в проект и переопределили метод render, чтобы делать все, что мы хотим.Это дает вам гибкость при определении точной взаимосвязи между родительскими элементами, которые необходимо отображать, а также при настройке стилей для любых создаваемых вами divs.

С другой стороны, вы сейчас кодируете, а не настраиваете, и для использования элемента управления необходимо внести изменения на главную страницу, которую вы используете.

На мой взгляд, оно того стоит.Теперь это стандартное изменение, которое мы вносим для любого сайта.

Подход, который мы использовали для достижения эффекта, который вы ищете, заключался в использовании Адаптеры управления, Дружественные к CSS.Адаптеры изменяют отображаемый HTML-код без изменения элементов управления, которые вы использовали на своих страницах.Возможно, вам потребуется немного подправить адаптер меню, чтобы получить желаемый макет.Для нас потребовалось всего несколько строк кода.Как только у вас это заработает, вы можете использовать CSS для получения описанного вами поведения.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top