I wish to override the title for the sitemap node, but only in the breadcrumb, not in the menu.
[Route("{partyAccountNumber}")]
[SiteMapTitle("DisplayName", Target = AttributeTarget.CurrentNode)]
public ActionResult Advice(string partyAccountNumber)
How do I use this overridden title, but only in the breadcrumb, and not in the menu.
You can put conditional logic on the title by editing the /Views/Shared/DisplayTemplates/SiteMapNodeModel.cshtml file directly.
#model MvcSiteMapProvider.Web.Html.Models.SiteMapNodeModel
#using System.Web.Mvc.Html
#using MvcSiteMapProvider.Web.Html.Models
#* Conditionally set the title based on a custom attribute *#
#{ var title = Model.Title; }
#if (Model.SourceMetadata["HtmlHelper"].ToString() == "MvcSiteMapProvider.Web.Html.SiteMapPathHelper" &&
Model.Attributes.ContainsKey("breadcrumbTitle") &&
!string.IsNullOrEmpty(Model.Attributes["breadcrumbTitle"].ToString()))
{
title = Model.Attributes["breadcrumbTitle"].ToString();
}
#if (Model.IsCurrentNode && Model.SourceMetadata["HtmlHelper"].ToString() != "MvcSiteMapProvider.Web.Html.MenuHelper") {
<text>#title</text>
} else if (Model.IsClickable) {
if (string.IsNullOrEmpty(Model.Description))
{
#title
}
else
{
#title
}
} else {
<text>#title</text>
}
On your node, you could set a custom attribute to provide the alternate value for the breadcrumb trail. If desired, you can also load the titles from a dynamic source, such as a database by using a dynamic node provider.
public class MyDynamicNodeProvider : DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
// Entities would be your entity framework context class
// or repository.
using (var entities = new Entities())
{
// Create a node for each item (data row in the table)
foreach (var item in entities.Items)
{
DynamicNode dynamicNode = new DynamicNode();
dynamicNode.Title = item.Title;
// Provide a custom attribute for the breadcrumb title.
dynamicNode.Attributes.Add("breadcrumbTitle", item.BreadcrumbTitle);
dynamicNode.ParentKey = "Home";
dynamicNode.Key = "Item_" + item.Id;
dynamicNode.Controller = "Item";
dynamicNode.Action = "Advice";
// custom route values must be manually accounted for by using
// RouteValues or PreservedRouteParameters.
dynamicNode.RouteValues.Add("partyAccountNumber", item.PartyAccountNumber);
//dynamicNode.PreservedRouteParameters.Add("partyAccountNumber");
yield return dynamicNode;
}
}
}
}
And then wire your dynamic node provider into your node configuration.
// Set a key explicitly to attach the dynamic nodes to.
// The key property here corresponds to the ParentKey property of the dynamic node.
<mvcSiteMapNode title="Home" controller="Home" action="Index" key="Home">
// Use a "dummy" node for the dynamic node provider. This node won't be in the SiteMap.
<mvcSiteMapNode dynamicNodeProvider="NamespaceName.MyDynamicNodeProivder, AssemblyName"/>
</mvcSiteMapNode>
Related
We have currently added the Warehouse/Bin Transfer to the Acumatica Mobile to allow users to do bin transfers via the Mobile versus using Acumatica.
We have added the QtyAvailable to the From and To Bin selectors in the with the SelectorContainer statement. However is there a way to filter out only those records for the item instead of showing all bins/location. This filter would only be on the from location. The to location would still show all locations.
To show only bin/locations with Qty. Available greater than 0, you should extend Where condition for LocationAvailAttribute decorating the INTran.LocationID field:
using PX.Data;
using System;
namespace PX.Objects.IN
{
public class INTransferEntryExt : PXGraphExtension<INTransferEntry>
{
[PXRemoveBaseAttribute(typeof(LocationAvailAttribute))]
[PXMergeAttributes(Method = MergeMethod.Append)]
[LocationAvailCst(typeof(INTran.inventoryID), typeof(INTran.subItemID),
typeof(INTran.siteID), typeof(INTran.tranType), typeof(INTran.invtMult))]
public virtual void INTran_LocationID_CacheAttached(PXCache sender)
{
}
public class LocationAvailCstAttribute : LocationAvailAttribute
{
public LocationAvailCstAttribute(Type inventoryType, Type subItemType,
Type siteIDType, Type TranType, Type InvtMultType)
: base(inventoryType, subItemType, siteIDType, TranType, InvtMultType)
{
var attr = _Attributes[_SelAttrIndex] as PXDimensionSelectorAttribute;
var dimAttr = attr.GetAttribute<PXDimensionAttribute>();
var selAttr = attr.GetAttribute<PXSelectorAttribute>();
var select = selAttr.GetSelect();
select = select.WhereAnd<Where<INLocationStatus.qtyAvail, Greater<Zero>>>();
var newAttr = new PXDimensionSelectorAttribute(DimensionName,
select.GetType(), typeof(INLocation.locationCD),
new Type[]
{
typeof(INLocation.locationCD),
typeof(INLocationStatus.qtyOnHand),
typeof(INLocationStatus.qtyAvail),
typeof(INLocationStatus.active),
typeof(INLocation.primaryItemID),
typeof(INLocation.primaryItemClassID),
typeof(INLocation.receiptsValid),
typeof(INLocation.salesValid),
typeof(INLocation.transfersValid),
typeof(INLocation.projectID),
typeof(INLocation.taskID)
});
_Attributes[_SelAttrIndex] = newAttr;
newAttr.ValidComboRequired = attr.ValidComboRequired;
newAttr.CacheGlobal = attr.CacheGlobal;
newAttr.DirtyRead = attr.DirtyRead;
newAttr.DescriptionField = attr.DescriptionField;
}
}
}
}
With the custom LocationAvailCstAttribute added on the INTran.LocationID field, Location selector will show only bins/locations, for which current Inventory Item Qty. Available is greater than 0:
In my custom Dashboard, I would like to show a list of data. When I am creating a custom view using Advance Find, I am using "next x days" filter. Can I set that 'X' dynamically from my custom field? Each row can have different X.
I can do this using SQL Reporting Services, but I would like to prefer normal list. Is there a way or do I have to use report?
Thank you.
Actually it looks impossible to do this without some workaround customization! (To my knowledge). But you could change condition filters with a plugin every time it loads. To do this, you could create a new entity and a numeral field on it. Every time the dashboard loads you could change the condition of the view by replacing the value from that entity. The below snippet helps you in the plugin:
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context from the service provider.
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
if (context.Mode == 0 && context.Stage == 20 && context.MessageName.Equals("RetrieveMultiple"))
{
if (context.InputParameters.Contains("Query"))
{
if (context.InputParameters["Query"] is QueryExpression)
{
QueryExpression objQueryExpression = (QueryExpression)context.InputParameters["Query"];
if (objQueryExpression.EntityName == "account")
{
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
ConditionExpression privateFlagCondition;
privateFlagCondition = new ConditionExpression()
{
AttributeName = "statustype",
Operator = ConditionOperator.Equal,
Values = { "1" }
};
FilterExpression newFilter = new FilterExpression()
{
FilterOperator = LogicalOperator.Or,
Conditions = { privateFlagCondition }
};
objQueryExpression.Criteria.AddFilter(newFilter);
}
}
}
}
}
I'm new to drupal, and very new to panels. I have a custom module which displays rss feed items based on user taxonomy. It displays the correct info as a block, but it needs to be on the users' dashboard page, which uses panels. When I try to insert it, it is always blank.
The code inserts a default view I already created, showing all feed items (1_feeds_defaults_feed_items) into a block. I can't edit it to work in a panel. I imagine that there are 10 different things I may have done wrong, but have tried every permutation I can think of.
<?php
//.this function generates a block and calls the second
//function for the content of this block
function custom_block($op = 'list', $delta = 0, $edit = array()) {
switch ($op) {
case 'list':
$block[0]['info'] = 'Custom View';
$block[2]['cache'] = BLOCK_NO_CACHE;
return $block;
break;
case 'view':
switch ($delta) {
case 0:
$block['subject'] = '';
$block['content'] = custom_userfeeds() ;
break;
}
return $block;
}
}
function custom_userfeeds() {
//finds the user id from argument on user page.
//You can also find the user id the way the page you linked me to did,
//but if you do it the way I am below it would allow admins
//to view other users feeds
$uid = arg(1);
//loads the profile node -- 'profile' is the profile content type.
$node = content_profile_load('profile', $uid);
//find the terms associated with the user's profile
if ($node && $node->taxonomy) {
foreach($node->taxonomy as $term) {
$terms[] = $term->tid;
}
}
//embeds a view with those terms passed to it.
View display is something like block_1 or page_1
if($terms) {
$t = implode('+',$terms);
return views_embed_view("1_feeds_defaults_feed_items","page_1", $t);
}
}
Here is how I fixed it in a view:
-Add Argument -> Taxonomy -> Taxonomy Term ID
-Provide default argument
-PHP Code
-PHP argument code:
global $user;
$query = "SELECT tid FROM {term_user} WHERE uid = %d";
$result = db_query($query, $user->uid);
if ($result) {
$terms = array();
while ($term = db_fetch_array($result)) {
$terms[] = $term['tid'];
}
if ($terms) {
$termargs = implode("+", $terms);
return $termargs;
}
}
else {
return FALSE;
}
-Check "Allow multiple terms per argument."
I need to show the ID field in the Edit Form of a sharepoint list.
There is a way to do it ? I tried a calculated field and nothing.
I know that I can see the ID field in the view, and if I show as a Access Mode.
I'm using WSS3.0
You can add the ID field to the form using some JavaScript in a CEWP.
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js">
</script>
<script type="text/javascript">
$(function() {
// Get the ID from the query string
var id = getQueryString()["ID"];
// Find the form's main table
var table = $('table.ms-formtable');
// Add a row with the ID in
table.prepend("<tr><td class='ms-formlabel'><h3 class='ms-standardheader'>ID</h3></td>" +
"<td class='ms-formbody'>" + id + " </td></tr>");
})
function getQueryString() {
var assoc = new Array();
var queryString = unescape(location.search.substring(1));
var keyValues = queryString.split('&');
for (var i in keyValues) {
var key = keyValues[i].split('=');
assoc[key[0]] = key[1];
}
return assoc;
}
</script>
There is an alternative method that doesn't use the jQuery library if you prefer to keep things lightweight.
You can do this by creating a custom edit form quite easily. I usually stick it into an HTML table rendered within a webpart. There may be a better way of doing that but it's simple and it works.
The key line you'll want to look at is spFormField.ControlMode. This tells SharePoint how to display the control (Invalid, Display, Edit, New). So what you'll want to do is check if your spField.InternalName == "ID" and if it is, set the ControlMode to be Display.
The rest is just fluff for rendering the rest of the list.
Hope this helps.
HtmlTable hTable = new HtmlTable();
HtmlTableRow hRow = new HtmlTableRow();
HtmlTableCell hCellLabel = new HtmlTableCell();
HtmlTableCell hCellControl = new HtmlTableCell();
SPWeb spWeb = SPContext.Current.Web;
// Get the list we are going to work with
SPList spList = spWeb.Lists["MyList"];
// Loop through the fields
foreach (SPField spField in spList.Fields)
{
// See if this field is not hidden or hide/show based on your own criteria
if (!spField.Hidden && !spField.ReadOnlyField && spField.Type != SPFieldType.Attachments && spField.StaticName != "ContentType")
{
// Create the label field
FieldLabel spLabelField = new FieldLabel();
spLabelField.ControlMode = _view;
spLabelField.ListId = spList.ID;
spLabelField.FieldName = spField.StaticName;
// Create the form field
FormField spFormField = new FormField();
// Begin: this is your solution here.
if (spField.InteralName == "ID")
{ spFormField.ControlMode = SPControlMode.Display; }
else
{ spFormField.ControlMode = _view; }
// End: the end of your solution.
spFormField.ListId = spList.ID;
spFormField.FieldName = spField.InternalName;
// Add the table row
hRow = new HtmlTableRow();
hTable.Rows.Add(hRow);
// Add the cells
hCellLabel = new HtmlTableCell();
hRow.Cells.Add(hCellLabel);
hCellControl = new HtmlTableCell();
hRow.Cells.Add(hCellControl);
// Add the control to the table cells
hCellLabel.Controls.Add(spLabelField);
hCellControl.Controls.Add(spFormField);
// Set the css class of the cell for the SharePoint styles
hCellLabel.Attributes["class"] = "ms-formlabel";
hCellControl.Attributes["class"] = "ms-formbody";
}
}
I'm trying to configure the Quick Launch menu to only display the ancestors and descendant nodes of the currently select node. The menu also needs to display all the childern of the root node. More simply:
Given a site map of:
RootSite
---SubSite1 = navigation set at "Display the current site, the navigation items below the current site, and the current site's siblings"
-----Heading1 = navigation set at "Display the same navigation items as the parent site"
-------Page1 = navigation set at "Display the same navigation items as the parent site"
-------Page2 = navigation set at "Display the same navigation items as the parent site"
-----Heading2 = navigation set at "Display the same navigation items as the parent site"
---SubSite2 = navigation set at "Display the current site, the navigation items below the current site, and the current site's siblings"
-----Heading1 = navigation set at "Display the same navigation items as the parent site"
SiteMapProvider configuration:
<PublishingNavigation:PortalSiteMapDataSource ID="SiteMapDS" Runat="server"
SiteMapProvider="CurrentNavSiteMapProvider" EnableViewState="true"
StartFromCurrentNode="true" ShowStartingNode="false"/>
The expected and actual behavior of the Quick Launch menu displayed at SubSite1 is:
---SubSite1
-----Heading1
-------Page1
-------Page2
-----Heading2
---SubSite2
The expected behavior of the menu after navigating to Heading1 of SubSite2:
---SubSite1
---SubSite2
-----Heading1
What I actually see after navigating to Heading1 of SubSite2:
---SubSite1
-----Heading1
-------Page1
-------Page2
-----Heading2
---SubSite2
-----Heading1
This does not match what I expect to see if I set the Heading1 navigation to "Display the
same navigation items as the parent site" and SubSite2 is set to "Display the current site, the navigation items below the current site, and the current site's siblings". I expect
Heading1 to inherit the navigation item of SubSite2 with the SubSite1 items collapsed from view. I've also played with the various
Trim... attributes without success. Any help will be greatly appreciated!
I followed #Nat's guidance into the murky world Sharepoint webparts to achieve the behavior I described above. My approach was to roll my own version of the MossMenu webpart that Microsoft has released through the ECM Team Blog. This code is based on the native AspMenu control. I used this control to "intercept" the native SiteMapDataSource injected into through DataSourceId attribute in the markup and create a new XML data source to exhibit the desired behavior. I've included the final source code at the end of this wordy answer. Here are the bits from the master page markup:
<%# 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"/>
...
I followed the excellent step-by-step instructions to create my custom web part in the comments section of the MossMenu webpart at "Wednesday, September 19, 2007 7:20 AM by Roel". In my googling, I also found something to configure a Sharepoint site to display exceptions in the same lovely way that ASP.NET does by making the web.config changes here.
I decided to call my custom behavior a "compact menu" so I created a UseCompactMenus property on the control. If you don't set this attribute in the markup to true, the control will behave identically to an AspMenu control.
My application has the user always starting from the home page at the site map root. I can have the custom control store the initial (complete) site map when the root page is displayed. This is stored in a static string for use in the customizing behavior. If you application doesn't follow this assumption, the control will not work as expected.
On the initial application page, only the direct child pages to the root page are displayed in the menu. Clicking on these menu nodes will open all the child nodes under it but keeps the sibling nodes "closed". If you click on one of the other sibling nodes, it collapses the current node and it opens the newly selected node. That's it, enjoy!!
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;
}
}
}
I personally don't like the html that the default menu provides (table based layout).
Fortunately the SharePoint team has released the code for that control.
What we have done is to include that code in a project and have overridden the render method to do whatever we want. This give you the flexibility to define the exact relationship between parents that needs to be display as well as setting the styles on any divs you create.
On the down side you are now coding, not configuring and a change needs to be made to the master page you are using to use the control.
Worth it in my opinion. This is now a standard change we make for any site.
The approach we used to accomplish the affect you are looking for was to use the CSS Friendly Control Adapters. The adapters change the HTML that is rendered without changing the controls you used on your pages. You may need to tweak the menu adapter a little bit in order to get the layout you want. It only took a few lines of code for us. Once you get that working, you can use CSS to obtain the behavior you describe.