MOSS 2007中"快速启动"菜单的显示行为不一致
我试图将"快速启动"菜单配置为仅显示当前选择节点的祖先节点和后代节点。菜单还需要显示根节点的所有子节点。更简单地说:
给出以下站点地图:
根站点
--- SubSite1 =导航设置为"显示当前站点,当前站点下方的导航项以及当前站点的同级"
----- Heading1 =导航设置为"显示与父站点相同的导航项"
------- Page1 =导航设置为"显示与父站点相同的导航项"
------- Page2 =导航设置为"显示与父站点相同的导航项"
----- Heading2 =导航设置为"显示与父站点相同的导航项"
--- SubSite2 =导航设置为"显示当前站点,当前站点下方的导航项以及当前站点的同级"
----- Heading1 =导航设置为"显示与父站点相同的导航项"
SiteMapProvider配置:
<PublishingNavigation:PortalSiteMapDataSource ID="SiteMapDS" Runat="server" SiteMapProvider="CurrentNavSiteMapProvider" EnableViewState="true" StartFromCurrentNode="true" ShowStartingNode="false"/>
SubSite1上显示的"快速启动"菜单的预期行为和实际行为是:
--- SubSite1
-----标题1
-------第1页
- -第2页
-----标题2
--- SubSite2
导航到SubSite2的Heading1后,菜单的预期行为:
--- SubSite1
--- SubSite2
-----标题1
导航到SubSite2的Heading1后,我实际上看到的是:
--- SubSite1
-----标题1
-------第1页
- -第2页
-----标题2
--- SubSite2
-----标题1
如果将Heading1导航设置为"显示
我希望与父站点相同的导航项"和SubSite2设置为"显示当前站点,当前站点下方的导航项以及当前站点的同级"。
Heading1继承了SubSite2的导航项,而SubSite1项从视图中折叠了起来。我也玩过各种
修剪...没有成功的属性。任何帮助将不胜感激!
解决方案
我个人不喜欢默认菜单提供的html(基于表格的布局)。
幸运的是,SharePoint团队已经发布了该控件的代码。
我们要做的是将该代码包含在项目中,并且重写了render方法以执行我们想要的任何操作。这使我们可以灵活地定义需要显示的父级之间的确切关系,以及在创建的所有div上设置样式。
不利的一面是,我们现在正在编码,而不是进行配置,并且需要对用于使用控件的母版页进行更改。
我认为值得。现在,这是我们对任何站点进行的标准更改。
我按照@Nat的指导进入了模糊的世界Sharepoint Webparts,以实现我上面描述的行为。我的方法是发布自己通过Microsoft通过ECM团队博客发布的MossMenu Webpart版本。此代码基于本机AspMenu控件。我使用此控件来"拦截"通过标记中的DataSourceId属性注入的本地SiteMapDataSource,并创建一个新的XML数据源以展现所需的行为。在这个罗word的答案的结尾,我已经包含了最终的源代码。以下是母版页标记中的内容:
<%@ 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的注释部分中创建了自定义Web部件,网址为" Roel,2007年9月19日,星期三,7:20。在我的谷歌搜索中,我还发现了一些配置SharePoint网站的方法,以便通过在此处进行web.config更改来以类似于ASP.NET的可爱方式显示异常。
我决定将自定义行为称为"紧凑菜单",因此在控件上创建了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; } } }
我们用来实现我们想要的效果的方法是使用CSS Friendly Control Adapters。适配器可以更改呈现的HTML,而无需更改我们在页面上使用的控件。我们可能需要稍微调整菜单适配器以获取所需的布局。我们只花了几行代码。一旦工作正常,就可以使用CSS获得我们描述的行为。