Javafx Pagination change pageCount issue - javafx-2

I create Pagination
pagination = PaginationBuilder.create().pageFactory(new Callback<Integer, Node>() {
#Override
public Node call(Integer pageIndex) {
return createChartPage(pageIndex);
}
}).build();
And i expect that total pages can be changed .
And when i setPageCount(totalPages). that change pageCountproprety which inform the pageFactory and make callback with index = 0 ;
- My Problem.
I want the page index not change when i change the PageCount.
In other word how can i removeChangeListener from pageCountProperty.

Use this workaround on the factory method until it's fixed in the JDK:
private Parent createPage(Integer pageIndex) {
// Fix for Pagination.getPageCount resetting the current index to 0
if (Math.abs(previousPageIndex - pageIndex) > 1) {
mainPagination.setCurrentPageIndex(previousPageIndex);
return root.get(previousPageIndex);
} else {
// Normal navigation
previousPageIndex = pageIndex;
return root.get(pageIndex);
}
}

Related

Getting object name and randomly placing it to Text UI - Unity

I am a beginner in Unity and I am currently making a simple game. I have a problem managing the flow of my minigame. The minigame is simply finding an object, when I found and tap on the item name below will be shaded or marked or there can be animation just to indicate that the item is found.
What I want to do is to get the name of the objects that need to be found and set them randomly in the three (3) item names below. like every time this minigame opens the names of the items are randomly placed in the 3 texts. And when the item is found the name below will be marked or shaded or anything that will indicate it is found, but for now I will just set it inactive for easier indication. How can I properly do this whole process?
The objects inside the scene are button for them to have onCLick() events
Correction: the term choices are wrong because they just display the name of the items that you need to find, just in case you get confused with the term choice in my minigame. I will fix it.
Here is the visuals for the minigame:
The script I currently have for when the objects was clicked:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using TMPro;
public class ClickObject : MonoBehaviour
{
[SerializeField] Button pillowBtn, pcBtn, lampBtn;
// [SerializeField] TextMeshProUGUI choice1, choice2, choice3;
// FROM THE SOLUTION
[SerializeField] List<GameObject> gameObjectWanted;
[SerializeField] List<TextMeshProUGUI> textBoxes;
// METHOD NAMES IS TEMPORARY
public void pillowClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
public void desktopClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
public void lampClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
}
You asked for a lot and I hope I understood your intention.
first, if you want to randomly choose an amount of game objects from your game, I think the best way to do it is by adding the refernece of all the game objects you want to choose from radomly inside a list of game objects and then randomly take a game object of the list and make it a child of another game object I call "fatherGoTranform" on my code like that:
[SerializeField] List<GameObject> gameObjectWanted;
[SerializeField] float numOfGO = 4;
[SerializeField] Transform fatherGoTranform;
void Start()
{
for(int i=0;i<numOfGO;i++)
{
int index = Random.Range(0, gameObjectWanted.Count-1);
GameObject currentGO = gameObjectWanted[index ];
currentGO.transform.parent = fatherGoTranform;
gameObjectWanted.RemoveAt(index);
}
}
and then to click on a game object and the do with what you want try this:
void Update()
{
//Check for mouse click
if (Input.GetMouseButtonDown(0))
{
RaycastHit raycastHit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out raycastHit, 100f))
{
if (raycastHit.transform != null)
{
//Our custom method.
CurrentClickedGameObject(raycastHit.transform.gameObject);
}
}
}
}
I have not checked the code so if there is an error tell me and I will fix it
This is the solution that worked for my problem. I randomly placed numbers to the list according to childCount and every index indicate the index of the text that I want to put them on which I get as a Transform child.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using TMPro;
public class ClickObject : MonoBehaviour
{
// [SerializeField] Button pillowBtn, pcBtn, lampBtn;
[SerializeField] GameObject choices, v1, finishPanel;
// RANDOM NUMBER HOLDER FOR CHECKING
private int randomNumber, foundCount;
// RANDOMIZED NUMBER LIST HOLDER
public List<int> RndmList = new List<int>();
private void Awake()
{
foundCount = 0;
RndmList = new List<int>(new int[v1.transform.childCount]);
for (int i = 0; i < v1.transform.childCount; i++)
{
randomNumber = UnityEngine.Random.Range(0, (v1.transform.childCount) + 1);
while (RndmList.Contains(randomNumber))
{
randomNumber = UnityEngine.Random.Range(0, (v1.transform.childCount) + 1);
}
RndmList[i] = randomNumber;
// Debug.Log(v1.transform.GetChild(randomNumber-1).name);
choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().text = v1.transform.GetChild(randomNumber - 1).name;
}
}
public void objectFound()
{
string objectName = EventSystem.current.currentSelectedGameObject.name;
Debug.Log("you found the " + objectName);
for (int i = 0; i < choices.transform.childCount; i++)
{
if (objectName == choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().text)
{
// Debug.Log(i);
// choices.transform.GetChild(i).gameObject.SetActive(false);
// choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().color = Color.gray;
if(RndmList.Contains(i+1))
{
// Debug.Log(i);
// Debug.Log(v1.transform.GetChild(RndmList[i]-1).name);
choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().color = Color.gray;
v1.transform.GetChild(RndmList[i]-1).GetComponent<Button>().enabled = false;
foundCount++;
}
}
}
if(foundCount == v1.transform.childCount)
{
finishPanel.SetActive(true);
}
}
}

Extend Umbraco back office search to search custom proerties not just title

What I would like to do is be able to type an custom property within the back office search. e.g. put the ISBN into the search field and have the results shown currently it always returns "no items found" as the search will only show results for the title node.
How do I enable the content search as seen in the image to search the data in the custom fields?
The data is in the internal index, I have checked the index is working and can see the result with "Examine Management" if I search via the custom data.
The solution is what I used to extend the search
https://dev.to/skttl/how-to-customize-searching-in-umbraco-list-views-1knk
Add a new file in the App_Code (SearchExtender)
using System.Linq;
using Examine;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.Editors;
using Umbraco.Web.Models.ContentEditing;
namespace SearchExtender
{
public class CustomListViewSearchController : ContentController
{
public CustomListViewSearchController(PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper)
: base(propertyEditors, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper)
{
}
public PagedResult<ContentItemBasic<ContentPropertyBasic>> GetChildrenCustom(int id, string includeProperties, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "", string cultureName = "")
{
// get the parent node, and its doctype alias from the content cache
var parentNode = Services.ContentService.GetById(id);
var parentNodeDocTypeAlias = parentNode != null ? parentNode.ContentType.Alias : null;
// if the parent node is not "books", redirect to the core GetChildren() method
if (parentNode?.ContentType.Alias != "books")
{
return GetChildren(id, includeProperties, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
}
// if we can't get the InternalIndex, redirect to the core GetChildren() method, but log an error
if (!ExamineManager.Instance.TryGetIndex("InternalIndex", out IIndex index))
{
Logger.Error<CustomListViewSearchController>("Couldn't get InternalIndex for searching products in list view");
return GetChildren(id, includeProperties, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
}
// find children using Examine
// create search criteria
var searcher = index.GetSearcher();
var searchCriteria = searcher.CreateQuery();
var searchQuery = searchCriteria.Field("parentID", id);
if (!filter.IsNullOrWhiteSpace())
{
searchQuery = searchQuery.And().GroupedOr(new [] { "nodeName", "isbn" }, filter);
}
// do the search, but limit the results to the current page 👉 https://shazwazza.com/post/paging-with-examine/
// pageNumber is not zero indexed in this, so just multiply pageSize by pageNumber
var searchResults = searchQuery.Execute(pageSize * pageNumber);
// get the results on the current page
// pageNumber is not zero indexed in this, so subtract 1 from the pageNumber
var totalChildren = searchResults.TotalItemCount;
var pagedResultIds = searchResults.Skip((pageNumber > 0 ? pageNumber - 1 : 0) * pageSize).Select(x => x.Id).Select(x => int.Parse(x)).ToList();
var children = Services.ContentService.GetByIds(pagedResultIds).ToList();
if (totalChildren == 0)
{
return new PagedResult<ContentItemBasic<ContentPropertyBasic>>(0, 0, 0);
}
var pagedResult = new PagedResult<ContentItemBasic<ContentPropertyBasic>>(totalChildren, pageNumber, pageSize);
pagedResult.Items = children.Select(content =>
Mapper.Map<IContent, ContentItemBasic<ContentPropertyBasic>>(content))
.ToList(); // evaluate now
return pagedResult;
}
}
}
change requests for /umbraco/backoffice/UmbracoApi/Content/GetChildren (the default endpoint for child nodes), and change it to my newly created one, which is located at /umbraco/backoffice/api/CustomListViewSearch/GetChildrenCustom.
This is done easily by adding a js file containing an interceptor like this.
Add file to /App_Plugins/CustomListViewSearch/CustomListViewSearch.js
angular.module('umbraco.services').config([
'$httpProvider',
function ($httpProvider) {
$httpProvider.interceptors.push(function ($q) {
return {
'request': function (request) {
// Redirect any requests for the listview to our custom list view UI
if (request.url.indexOf("backoffice/UmbracoApi/Content/GetChildren?id=") > -1)
request.url = request.url.replace("backoffice/UmbracoApi/Content/GetChildren", "backoffice/api/CustomListViewSearch/GetChildrenCustom");
return request || $q.when(request);
}
};
});
}]);
a package.manifest file in my App_Plugins folder.
{
"javascript": [
"/App_Plugins/CustomListViewSearch/CustomListViewSearch.js"
]
}
If the node Alais isnot working make sure its set in the documnt type (far right on document type name)

How to return top previous URL after Edit in ASP.NET Core

I am using asp,net core and have used the tutorial to create sorted, paged and search page (Index). Once I edit an item from this page the controller always dumps me back to the default index page. How do I return to the previous URL. Many thanks.
Here is a section of my controller file.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, Bind("id,UserPassword,user")] UserProfiles userProfiles)
{
var users = from u in _context.UserProfiles
select u;
if (id != userProfiles.id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(userProfiles);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!UserProfilesExists(userProfiles.id))
{
return NotFound();
}
else
{
throw;
}
}
// ***************
// Redirect to the previous URL,i.e. the Index
return Redirect(TempData["PreviousURL"].ToString()) ;
}
return View(userProfiles);
}
public async Task<IActionResult> Index(string sortOrder, string currentFilter, string searchString, int? page)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
// paging
if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}
// search
ViewData["CurrentFilter"] = searchString;
var users = from u in _context.UserProfiles
select u;
if (!String.IsNullOrEmpty(searchString))
{
users = users.Where(u => u.user.Contains(searchString)
);
}
//sort
switch (sortOrder)
{
case "name_desc":
users = users.OrderByDescending(u => u.user);
break;
default:
users = users.OrderBy(s => s.user);
break;
}
// ***************
// store the current path and query string in TempData["PreviousURL" session variable
TempData["PreviousURL"] = HttpContext.Request.Path.ToString() + HttpContext.Request.QueryString.ToString();
return View(await PaginatedList<UserProfiles>.CreateAsync(users.AsNoTracking(), page ?? 1, pageSize));
}
This is my first MVC project.
It depends on your logic where controller takes you after saving data.
You need to pass search, sort and paging related data to controller when saving data. You can send them as part of extra post data, as query string parameters or as part of the model itself which is being posted.
After saving data retrieve data based on those parameters and populater your view with that paged, filtred and sorted data.
I solved my problem with the use of session variables: ViewData, ViewBag and TempData. The following two pages were very useful:
https://www.codeproject.com/Articles/476967/What-is-ViewData-ViewBag-and-TempData-MVC-Option
http://andrewlock.net/an-introduction-to-session-storage-in-asp-net-core/
Please see edited question above for the solution.

MonoTouch Dialog elements are not updating/repainting themselves

I have the following in a Section:
_favElement = new StyledStringElement (string.Empty);
_favElement.Alignment = UITextAlignment.Center;
if (_room.IsFavourite) {
_favElement.Image = UIImage.FromBundle ("Images/thumbs_up.png");
_favElement.Caption = "Unmark as Favourite";
} else {
_favElement.Image = null;
_favElement.Caption = "Mark as Favourite";
}
_favElement.Tapped += favElement_Tapped;
Then when I press the element I want the following to happen:
private void favElement_Tapped ()
{
if (_room.IsFavourite) {
_favElement.Image = null;
_favElement.Caption = "Mark as Favourite";
} else {
_favElement.Image = UIImage.FromBundle ("Images/thumbs_up.png");
_favElement.Caption = "Unmark as Favourite";
}
_room.IsFavourite = !_room.IsFavourite;
}
However the image and text does not change in the actual element when the element is tapped. Is there a refresh method or something that must be called? I've also tried changing the Accessory on Tapped as well and nothing changes. The properties behind do reflect the correct values though.
An alternative to reloading the UITableView is to reload the Element using code like this (copied from Touch.Unit):
if (GetContainerTableView () != null) {
var root = GetImmediateRootElement ();
root.Reload (this, UITableViewRowAnimation.Fade);
}
assuming that your code is in DialogViewController,add this
this.ReloadData();
but in your case I recommend you to use BooleanImageElement

Inconsistent display behavior for Quick Launch menu in MOSS 2007

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.

Resources