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获得我们描述的行为。

