SiteMapNode Attributes - values being lost (SharePoint 2010) - sharepoint

OK - I've created a custom navigation provider that inherits PortalSiteMapProvider and I'm trying to extend my class via the GetChildNodes method (Simple example here).
Essentially what I'm trying to do is add the current SPWeb's SiteLogoUrl string as an attribute (called imgurl) of each SPWeb's respective SiteMapNode, with a view to retrieve this value later in my custom Menu control (which inherits SharePoint:AspMenu).
I'm setting the attributes like so in my provider under GetChildNodes:
public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
{
PortalSiteMapNode portalNode = (PortalSiteMapNode)node;
if (portalNode != null)
{
if (portalNode.Type == NodeTypes.Area)
{
SiteMapNodeCollection nodeColl = base.GetChildNodes(portalNode);
using (SPSite currentSite = new SPSite(portalNode.PortalProvider.CurrentSite.Url))
{
foreach (SiteMapNode topLevelNode in nodeColl)
{
foreach(SiteMapNode currentNode in topLevelNode.ChildNodes)
{
string currentWebUrl = currentNode.Url.Substring(0, currentNode.Url.ToLower().IndexOf("/pages/"));
using (SPWeb currentWeb = currentSite.OpenWeb(currentWebUrl))
{
if (!string.IsNullOrEmpty(currentWeb.SiteLogoUrl))
{
currentNode["imgurl"] = currentWeb.SiteLogoUrl;
}
}
}
}
}
return nodeColl;
}
Now I can debug this and retrieve the value once it has been inserted like so (x being the index of node, and me doing this in the Command Window):
? topLevelNode.ChildNodes[x]["imgurl"]
and that returns the url of the image fine : "/_layouts/images/myimage.jpg"
Now.. when I try to do this in my custom Menu control I first get a handle on my custom nav provider like so:
CustomNavProvider customProvider = (CustomNavProvider)SiteMap.Providers["CustomNavProvider"];
SiteMapNode currentNode = customProvider.FindSiteMapNode(childitem.NavigateUrl);
and the currentNode variable now contains my SiteMapNode...great, however instead of the attribute I added previously, there is now only a single attribute called 'AlternateUrl' - the imgurl attribute I added in the provider has now disappeared. All other properties like title, desc etc are there so it's definitely returning the correct node.
What's my issue here? Is the attribute not being persisted when I added it to the node in the provider?
Any help or suggestions appreciated.

Related

Orchard alternates based on Tag

I want to create alternates for content item based on its tag value.
For example, I want to create an alternate called List-ProjectionPage-tags-special
Searching the nets directs me to implement a new ShapeDisplayEvents
Thus, I have
public class TagAlternatesFactory : ShapeDisplayEvents
{
public TagAlternatesFactory()
{
}
public override void Displaying(ShapeDisplayingContext context)
{
}
}
In the Displaying method, I believe I need to check the contentItem off the context.Shape and create an alternate name based off of that (assuming it has the TagsPart added to the content item).
However, what do I do with it then? How do I add the name of the alternate? And is that all that's needed to create a new alternate type? Will orchard know to look for List-ProjectionPage-tags-special?
I took a cue from Bertrand's comment and looked at some Orchard source for direction.
Here's my implementation:
public class TagAlternatesFactory : ShapeDisplayEvents
{
public override void Displaying(ShapeDisplayingContext context)
{
context.ShapeMetadata.OnDisplaying(displayedContext =>
{
var contentItem = displayedContext.Shape.ContentItem;
var contentType = contentItem.ContentType;
var parts = contentItem.Parts as IEnumerable<ContentPart>;
if (parts == null) return;
var tagsPart = parts.FirstOrDefault(part => part is TagsPart) as TagsPart;
if (tagsPart == null) return;
foreach (var tag in tagsPart.CurrentTags)
{
displayedContext.ShapeMetadata.Alternates.Add(
String.Format("{0}__{1}__{2}__{3}",
displayedContext.ShapeMetadata.Type, (string)contentType, "tag", tag.TagName)); //See update
}
});
}
}
This allows an alternate view based on a tag value. So, if you have a project page that you want to apply a specific style to, you can simply create your alternate view with the name ProjectionPage_tag_special and anytime you want a projection page to use it, just add the special tag to it.
Update
I added the displayedContext.ShapeMetadata.Type to the alternate name so specific shapes could be overridden (like the List-ProjectionPage)

Create Orchard ContentItem programmatically from Command

I've written the following simple command inside my module. The Faq type has one custom part with a single field, and one BodyPart. After _cm.Create(item) is run, the item has an Id assigned but I can't find any trace of it in the database and it doesn't appear in Orchard's content tab. Why does the item get an Id but isn't found in the database? And does it need a driver, view, and placement info before it appears in the content tab?
public class ApiCommands : DefaultOrchardCommandHandler
{
private readonly IContentManager _cm;
public ApiCommands(IContentManager cm)
{
_cm = cm;
}
[CommandName("api seed")]
public void Seed()
{
var item = _cm.New("Faq");
item.As<FaqPart>().Question = "Why is the sky blue?";
item.As<BodyPart>().Text = "Shut up and do your homework.";
_cm.Create(item);
}
}
My custom part has no driver this is the Handler:
public FaqHandler(IRepository<FaqPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
It turns out my type didn't attach a CommonPart. After I attached one and set the Owner property of the part, I was able to save it.

Finding control on SharePoint page

Im trying to locate an SPDataSource control located on my SharePoint page. I found the following code which probably works fine, I just don't know what to pass into it.
public static Control FindControlRecursive(Control Root, string Id)
{
if (Root.ID == Id)
return Root;
foreach (Control Ctl in Root.Controls)
{
Control FoundCtl = FindControlRecursive(Ctl, Id);
if (FoundCtl != null)
return FoundCtl;
}
return null;
}
I don't know how to have it search the whole page or at the very least the ContentPlaceHolder that the control is in.
edit
Looks like I have a more rudimentary issue here. Not sure how to explain but I'm not opening up the page before running my code. I'm opening the site via the following:
using (SPWeb web = thisSite.Site.OpenWeb("/siteurl/,true))
So when I try to find the page below I'm getting Object reference not set to instance of object.
var page = HttpContext.Current.Handler as Page;
Perhaps I'm going about this the wrong way, I'm in my infancy here so I'm just kind of stumbling along figuring stuff out!
What you got is actually not SharePoint specific, it's c# asp.net.
Anyway, you could call it like this
var page = HttpContext.Current.Handler as Page;
var control = page; // or put the element you know exist that omit (is a parent) of the element you want to find
var myElement = FindControlRecursive(control, "yourelement");
Most likely you'll need to cast the return as well
var myElement = (TextBox)FindControlRecursive(control, "yourelement");
// or
var myElement = FindControlRecursive(control, "yourelement") as TextBox;
There are however more efficient ways to write such a method, here is one simple example
public static Control FindControlRecursive(string id)
{
var page = HttpContext.Current.Handler as Page;
return FindControlRecursive(page, id);
}
public static Control FindControlRecursive(Control root, string id)
{
return root.ID == id ? root : (from Control c in root.Controls select FindControlRecursive(c, id)).FirstOrDefault(t => t != null);
}
Call it the same way as I suggested earlier.
If you are handling larger pages the methods above might be a bit slow, what you should do is aim for a method using generics instead. They are way faster than traditional methods.
Try this one
public static T FindControlRecursive<T>(Control control, string controlID) where T : Control
{
// Find the control.
if (control != null)
{
Control foundControl = control.FindControl(controlID);
if (foundControl != null)
{
// Return the Control
return foundControl as T;
}
// Continue the search
foreach (Control c in control.Controls)
{
foundControl = FindControlRecursive<T>(c, controlID);
if (foundControl != null)
{
// Return the Control
return foundControl as T;
}
}
}
return null;
}
You call it like this
var mytextBox = FindControlRecursive<TextBox>(Page, "mytextBox");

How to access the Sharepoint SPNavigationNode.QuickLaunch Property?

I have a site as follows:
--SiteA
----Subsite1
----Subsite2
Now whenever i try to access the QuickLaunch Property its always empty e.g
SPNavigation nav = spWeb.Navigation;
if (nav.QuickLaunch.Count == 0)
{
// ALWAYS TRUE
}
However if i go into the Naviation Settings (Through the UI) of SiteA and reorder any site in the list, only then will the QuickLanuch become available. (Other settings are left as default)
Can anyone explain this behaviour? I really need access to the QuickLaunch items.
Thanks
This error occurs if you access quicklaunch while site is being created.Below code causes the feature activated code to wait until the site collection has been created before executing.
using System.Threading;
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
//Queues changes until after site exists. For use in provisioning.
SPWeb web = properties.Feature.Parent as SPWeb;
ThreadPool.QueueUserWorkItem(ApplyYourChanges, web.Url);
}
private void ApplyYourChanges(object state)
{
string webUrl = state as string;
Uri uri = new Uri(webUrl);
// additional conditions here -- perhaps check if a feature was activated
while (!SPSite.Exists(uri))
{
Thread.Sleep(5000);
}
using (SPSite site = new SPSite(webUrl))
{
using (SPWeb web = site.OpenWeb())
{
//configure the quicklaunch menu
configureQuickLaunch(web);
}
}
}
public static void configureQuickLaunch(SPWeb spWeb)
{
SPNavigationNodeCollection nodeCollection = spWeb.Navigation.QuickLaunch;
SPNavigationNode heading = nodeCollection.Cast<SPNavigationNode>().FirstOrDefault(n => n.Title == headingNode);
SPNavigationNode item = heading.Children.Cast<SPNavigationNode>().FirstOrDefault(n => n.Url == url);
if(item == null)
{
item = new SPNavigationNode(nodeName, url);
item = heading.Children.AddAsLast(item);
}
}
I think, by default, the QuickLaunch uses shared navigation. In other words, the QuickLaunch for a subsite doesn't have its own collection of nodes until you do something with it. If you reorder a site, that gives it its own unique set of nodes.
If you wanted to programmatically set your QuickLaunch to have its own set of nodes programmatically, you should be able to do so this way:
SPNavigation nav = spWeb.Navigation;
nav.UseShared = false;
spWeb.Update();
I think your count should be something other than zero at that point.
I seem to remember reading somewhere that the QuickLaunch collection only stored customisations to the default ordering. Looking around, I can't find that documentation to show you, but it would explain the behavior you see if true.
So your QuickLaunch.Count == 0 is just confirming that default ordering of items is in place.
You can still add nodes, if that's at all helpful;
SPNavigationNodeCollection nodes = web.Navigation.QuickLaunch;
SPNavigationNode node = new SPNavigationNode("Node Name", "Node URL", true);
nodes.AddAsFirst(node);

Sharepoint Custom Filter Web Part

I want to create a custom web part that has more than 1 filter web part and that can be connected to Report Viewer Web Part (Integrated Mode) at runtime/design time.
I searched a lot for this, but could not find a way to have single web part that is a provider to more than 1 filters.
Say for example -
My Report accepts 2 parameter Department and Region. 
I want to connect both parameters with single web part having two drop down (one for Department and one for Region)
Values from both the drop down should be passed to Department and Region
Report should be rendered in Report Viewer Web Part
Solution Tried so far
Create a web part that adds two custom drop down
Custom drop down class that implements from ITransformableFilterValues
Have 2 methods on the web pat each having ConnectionProvider attribute and return instance of drop down control
Problem:
Even though 2 connection option is shown on my custom filter web part only one can be added.
For example if I connect Filter1(custom web part) to Department then I am unable to connect it to Report Viewer web part again.
My web part have methods like this:
 
[ConnectionProvider("Departmet", "UniqueIDForDept", AllowsMultipleConnections = true)] 
public ITransformableFilterValues ReturnCity() 
{ 
return dropDownDepartment; // It implemets ITransformableFilterValues 
} 
[ConnectionProvider("Region", "UniqueIDForRegion", AllowsMultipleConnections = true)] 
public ITransformableFilterValues ReturnMyRegionB() 
{ 
return dropDownRegion; //It implemets ITransformableFilterValues 
}
I did something similar. This might help point you in the right direction. I used data in a form library to create a detailed report. I used reporting services and connected to sharepoint using web services. http://server/_vti_bin/Lists.asmx. The report parameter I used was the item ID or GUID. Then I configured my report viewer. On the form library I used JavaScript to override the context menu to add "View Report". On the report page I used a Query String filter to grab the item ID out of the url.
Not sure if you were able to fix your problem..
Actually I tried with AllowsMultipleConnections = true and it worked fine:
using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using aspnetwebparts = System.Web.UI.WebControls.WebParts;
using Microsoft.Office.Server.Utilities;
using wsswebparts = Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.Portal.WebControls;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using Microsoft.SharePoint.Utilities;
namespace FromMultiSource
{
[Guid("a0d068dd-9475-4055-a219-88513e173502")]
public class MultiSource : aspnetwebparts.WebPart
{
List<wsswebparts.IFilterValues> providers = new List<wsswebparts.IFilterValues>();
public MultiSource()
{
}
[aspnetwebparts.ConnectionConsumer("Multiple Source Consumer", "IFilterValues", AllowsMultipleConnections = true)]
public void SetConnectionInterface(wsswebparts.IFilterValues provider)
{
this.providers.Add(provider);
if (provider != null)
{
List<wsswebparts.ConsumerParameter> l = new List<wsswebparts.ConsumerParameter>();
l.Add (new wsswebparts.ConsumerParameter ("Value", wsswebparts.ConsumerParameterCapabilities.SupportsMultipleValues | Microsoft.SharePoint.WebPartPages.ConsumerParameterCapabilities.SupportsAllValue));
provider.SetConsumerParameters(new ReadOnlyCollection<wsswebparts.ConsumerParameter>(l));
}
}
protected override void CreateChildControls()
{
base.CreateChildControls();
// TODO: add custom rendering code here.
// Label label = new Label();
// label.Text = "Hello World";
// this.Controls.Add(label);
}
protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
this.EnsureChildControls();
foreach (wsswebparts.IFilterValues provider in this.providers)
{
if (provider != null)
{
string prop = provider.ParameterName;
ReadOnlyCollection<string> values = provider.ParameterValues;
if (prop != null && values != null)
{
writer.Write("<div>" + SPEncode.HtmlEncode(prop) + ":</div>");
foreach (string v in values)
{
if (v == null)
{
writer.Write("<div> <i>"(empty)"/null</i></div>");
}
else if (v.Length == 0)
{
writer.Write("<div> <i>empty string</i></div>");
}
else
{
writer.Write("<div> " + v + "</div>");
}
}
}
else
{
writer.Write("<div>No filter specified (all).</div>");
}
}
else
{
writer.Write("<div>Not connected.</div>");
}
writer.Write("<hr>");
}
}
}
}

Resources