As you can see from the image, the ID attribute of the DIV is a GUID, and this GUID is the Feature ID. Simple.
Friday, 2 September 2011
Finding Feature IDs
If you're anything like me, sometimes you need to find out what the ID of a SharePoint feature is and it can be something of a trial to locate the exact ID, especially if there's no handy SharePoint Server to remote onto and then go and search the FEATURES directory. So, a much easier way to get the ID of a feature is to use the Developer Toolbar (if you're using IE). Turn on the developer toolbar and select the Activate button of the feature you're interested in using the element selector. The button is enclosed in a DIV which has an ID attribute as follows:

As you can see from the image, the ID attribute of the DIV is a GUID, and this GUID is the Feature ID. Simple.
As you can see from the image, the ID attribute of the DIV is a GUID, and this GUID is the Feature ID. Simple.
Monday, 22 August 2011
Getting today's date and current user in CQWP
If you've ever used a Data Form Web Part you'll know that you can set up some Parameter Bindings to pull in various SharePoint-specific values like querystring parameters and server/environment variables etc. It's only a small leap of faith to understand that since the Content by Query Web Part derives from the same web part these parameter bindings may also be available to the Content by Query Web Part and therefore able to be exposed in the XSL using the xsl:param command.
I've used the current Page Url and querystring in 2010 and have since used it in 2007 using the above tweaking for CQWPs hard-coded into a page layout. Two more useful values I came across were the User ID, which is the currently logged on user returned as a the User Name, and today's date returned using the ISO format we're familiar with in SharePoint. To use these, simply add a Parameter Binding as follows to a Parameter Bindings section in the CQWP parameters and then add an xsl:param into the XSL and hey-presto.
Add this section to your the CQWP HTML in your page:
<ParameterBindings>
<ParameterBinding Name="UserID" Location="CAMLVariable" DefaultValue="CurrentUserName"/>
<ParameterBinding Name="Today" Location="CAMLVariable" DefaultValue="CurrentDate"/>
</ParameterBindings>
and in the XSL add the following:
<xsl:param name="UserID />
<xsl:param name="Today" />
then use them as $Today and $UserID in the XSL.
I've used the current Page Url and querystring in 2010 and have since used it in 2007 using the above tweaking for CQWPs hard-coded into a page layout. Two more useful values I came across were the User ID, which is the currently logged on user returned as a the User Name, and today's date returned using the ISO format we're familiar with in SharePoint. To use these, simply add a Parameter Binding as follows to a Parameter Bindings section in the CQWP parameters and then add an xsl:param into the XSL and hey-presto.
Add this section to your the CQWP HTML in your page:
<ParameterBindings>
<ParameterBinding Name="UserID" Location="CAMLVariable" DefaultValue="CurrentUserName"/>
<ParameterBinding Name="Today" Location="CAMLVariable" DefaultValue="CurrentDate"/>
</ParameterBindings>
and in the XSL add the following:
<xsl:param name="UserID />
<xsl:param name="Today" />
then use them as $Today and $UserID in the XSL.
Labels:
Content Query Web Part,
SharePoint 2007,
SharePoint 2010,
XSL
Monday, 23 May 2011
Sending Emails using User/Sandboxed Solutions
If you've done any kind of work emailing using SharePoint you are probably aware of the SendEmail method available through the SharePoint object model as part of the Microsoft.SharePoint.Utilities namespace. It's very useful as it allows us to send emails with a minimum amount of effort and without having to know the SMTP settings and such like.
In user/sandboxed solutions, this method is not available so we're left in a bit of a limbo, not knowing how to achieve it as there's nothing else available in the object model that achieves the same thing. Let's say we have a custom "Contact Us" webpart which sends someone an email with the contact information: how can you achieve the same thing?
We could use the System.Net.Mail methods available to us, but is it reasonable to expect a content author to supply the webpart with the appropriate SMTP settings? Why is that an issue? Well, these settings can be accessed programmatically using the object model which would resolve this minor difficulty, but they are not available in a sandbox. They exist at Web Application level and are therefore off limits.
Fortunately, there is another way, and that is through using SharePoint Designer workflow. You can trigger a workflow to run programmatically and also supply information to the workflow and set the initiation values. Here's the code to start a workflow:
/// <summary>
/// Method to handle starting the appropriate Workflow as specified by the Site Collection Workflow Name property
/// </summary>
private void StartWorkflow()
{
SPWorkflowAssociation wfa = SPContext.Current.Web.WorkflowAssociations.GetAssociationByName(this.SiteCollectionWorkflowName, CultureInfo.InvariantCulture);
wfa.AssociationData = this.GetWorkflowInitiationData();
SPContext.Current.Site.WorkflowManager.StartWorkflow(null, wfa, this.GetWorkflowInitiationData(), SPWorkflowRunOptions.Asynchronous);
}
The StartWorkflow method is where the workflow starts and the GetWorkflowInitiationData is where the XML erquired to set the initiation properties are set up. From what I've seen, the XML stays pretty-much the same with properties being set using the "dataFields" namespace "d:". The property names are workflow initiation properties I set up in my workflow. The SiteCollectionWorkflowName used in the StartWorkflow method is a property of the webpart which could be hidden away.
In user/sandboxed solutions, this method is not available so we're left in a bit of a limbo, not knowing how to achieve it as there's nothing else available in the object model that achieves the same thing. Let's say we have a custom "Contact Us" webpart which sends someone an email with the contact information: how can you achieve the same thing?
We could use the System.Net.Mail methods available to us, but is it reasonable to expect a content author to supply the webpart with the appropriate SMTP settings? Why is that an issue? Well, these settings can be accessed programmatically using the object model which would resolve this minor difficulty, but they are not available in a sandbox. They exist at Web Application level and are therefore off limits.
Fortunately, there is another way, and that is through using SharePoint Designer workflow. You can trigger a workflow to run programmatically and also supply information to the workflow and set the initiation values. Here's the code to start a workflow:
/// <summary>
/// Method to handle starting the appropriate Workflow as specified by the Site Collection Workflow Name property
/// </summary>
private void StartWorkflow()
{
SPWorkflowAssociation wfa = SPContext.Current.Web.WorkflowAssociations.GetAssociationByName(this.SiteCollectionWorkflowName, CultureInfo.InvariantCulture);
wfa.AssociationData = this.GetWorkflowInitiationData();
SPContext.Current.Site.WorkflowManager.StartWorkflow(null, wfa, this.GetWorkflowInitiationData(), SPWorkflowRunOptions.Asynchronous);
}
/// <summary>
/// Method to construct the Workflow Initiation/Association Data
/// </summary>
/// <returns>A string containing the XML schema and values necessary for the Association Data</returns>
private String GetWorkflowInitiationData()
{
String schema = "<dfs:myFields xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
"xmlns:dms=\"http://schemas.microsoft.com/office/2009/documentManagement/types\" " +
"xmlns:dfs=\"http://schemas.microsoft.com/office/infopath/2003/dataFormSolution\" " +
"xmlns:q=\"http://schemas.microsoft.com/office/infopath/2009/WSSList/queryFields\" " +
"xmlns:d=\"http://schemas.microsoft.com/office/infopath/2009/WSSList/dataFields\" " +
"xmlns:ma=\"http://schemas.microsoft.com/office/2009/metadata/properties/metaAttributes\" " +
"xmlns:pc=\"http://schemas.microsoft.com/office/infopath/2007/PartnerControls\" " +
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<dfs:queryFields /><dfs:dataFields><d:SharePointListItem_RW>{0}</d:SharePointListItem_RW>" +
"</dfs:dataFields></dfs:myFields>";
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<d:EmailTo>{0}</d:EmailTo>", this.SendToEmailAddress);
sb.AppendFormat("<d:EmailSubject>{0}</d:EmailSubject>", this._subject.Text);
sb.AppendFormat("<d:EmailText>{0}</d:EmailText>", this._comments.Text);
sb.AppendFormat("<d:AutoResponseTo>{0}</d:AutoResponseTo>", this._email.Text);
sb.AppendFormat("<d:AutoResponseSubject>{0}</d:AutoResponseSubject>", this.AutoResponseSubject);
sb.AppendFormat("<d:AutoResponseText>{0}</d:AutoResponseText>", this.AutoResponseText);
return String.Format(schema, sb.ToString());
}
/// Method to construct the Workflow Initiation/Association Data
/// </summary>
/// <returns>A string containing the XML schema and values necessary for the Association Data</returns>
private String GetWorkflowInitiationData()
{
String schema = "<dfs:myFields xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
"xmlns:dms=\"http://schemas.microsoft.com/office/2009/documentManagement/types\" " +
"xmlns:dfs=\"http://schemas.microsoft.com/office/infopath/2003/dataFormSolution\" " +
"xmlns:q=\"http://schemas.microsoft.com/office/infopath/2009/WSSList/queryFields\" " +
"xmlns:d=\"http://schemas.microsoft.com/office/infopath/2009/WSSList/dataFields\" " +
"xmlns:ma=\"http://schemas.microsoft.com/office/2009/metadata/properties/metaAttributes\" " +
"xmlns:pc=\"http://schemas.microsoft.com/office/infopath/2007/PartnerControls\" " +
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<dfs:queryFields /><dfs:dataFields><d:SharePointListItem_RW>{0}</d:SharePointListItem_RW>" +
"</dfs:dataFields></dfs:myFields>";
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<d:EmailTo>{0}</d:EmailTo>", this.SendToEmailAddress);
sb.AppendFormat("<d:EmailSubject>{0}</d:EmailSubject>", this._subject.Text);
sb.AppendFormat("<d:EmailText>{0}</d:EmailText>", this._comments.Text);
sb.AppendFormat("<d:AutoResponseTo>{0}</d:AutoResponseTo>", this._email.Text);
sb.AppendFormat("<d:AutoResponseSubject>{0}</d:AutoResponseSubject>", this.AutoResponseSubject);
sb.AppendFormat("<d:AutoResponseText>{0}</d:AutoResponseText>", this.AutoResponseText);
return String.Format(schema, sb.ToString());
}
The StartWorkflow method is where the workflow starts and the GetWorkflowInitiationData is where the XML erquired to set the initiation properties are set up. From what I've seen, the XML stays pretty-much the same with properties being set using the "dataFields" namespace "d:". The property names are workflow initiation properties I set up in my workflow. The SiteCollectionWorkflowName used in the StartWorkflow method is a property of the webpart which could be hidden away.
Monday, 16 May 2011
Google Maps vs Bing Maps
One of the nice things about Google Maps is the ability to embed a static image of your map, rather than having to use a JavaScript API or resorting to downloading multiple JavaScript files to support the map's features. Simply using a URL to point to one of Google's services was enough when it was used as the SRC attribute of an IMG tag - simple. It was something that was missing, until fairly recently, from Bing Maps.
Fortunately, Bing Maps have caught up and now have a REST API available to support static imagery in the same way as Google Maps. The details can be found here: http://msdn.microsoft.com/en-us/library/ff701724.aspx.
Simply adding the correct URL to the SRC attribute of an IMG tag creates the correct image to embed thereby avoiding multiple JavaScript downloads and any use of a client-side API.
Fortunately, Bing Maps have caught up and now have a REST API available to support static imagery in the same way as Google Maps. The details can be found here: http://msdn.microsoft.com/en-us/library/ff701724.aspx.
Simply adding the correct URL to the SRC attribute of an IMG tag creates the correct image to embed thereby avoiding multiple JavaScript downloads and any use of a client-side API.
Publishing Images and Search Indexing
This may be something that you already know, but in SharePoint 2007 fields based on the Publishing Image field type caused problems when you wanted to use these kinds of field in the Search Results.
The problem became apparent if you wanted to use something like a Roll Up Image on content pages where you wanted to display the image both using Content Query Web Parts (which don't have a problem with Publishing Image fields) and in Search Results. The problem is, Search doesn't index fields of this type. It just doesn't. So you can set up your metadata property which you can add a property mapping for the Publishing Rollup Image field but when you try to surface the data contained by the property in search it contains nothing.
Having experienced this problem in 2007 during a custom Search project, I was already aware of the limitation and then SharePoint 2010 came around and a similar requirement came about. I was approached to take a look into whether it was still a problem and had some expectation that perhaps it had been resolved. Alas, it has not.
So, buyer beware!!
Fortunately, there is a simple work-around. Create a text field into which you automatically add the contents from your Publishing Image field as text by whatever means you prefer and then use that to set up your metadata property instead. Works like a charm.
The problem became apparent if you wanted to use something like a Roll Up Image on content pages where you wanted to display the image both using Content Query Web Parts (which don't have a problem with Publishing Image fields) and in Search Results. The problem is, Search doesn't index fields of this type. It just doesn't. So you can set up your metadata property which you can add a property mapping for the Publishing Rollup Image field but when you try to surface the data contained by the property in search it contains nothing.
Having experienced this problem in 2007 during a custom Search project, I was already aware of the limitation and then SharePoint 2010 came around and a similar requirement came about. I was approached to take a look into whether it was still a problem and had some expectation that perhaps it had been resolved. Alas, it has not.
So, buyer beware!!
How To Resolve
Fortunately, there is a simple work-around. Create a text field into which you automatically add the contents from your Publishing Image field as text by whatever means you prefer and then use that to set up your metadata property instead. Works like a charm.
Labels:
SharePoint 2007,
SharePoint 2010,
SharePoint Search
Wednesday, 13 April 2011
Removing Unnecessary CSS
I've been recently looking at performance of public-facing websites built with SharePoint 2010 and, following on from Chris O'Brien's blog on removing unnecessary JavaScript files for anonymous users that are loaded as part of the Script-on-Demand framework, I was browsing through the kinds of files requested and whether they were important or not.
One thing I noticed was that there are several CSS files also loaded by SharePoint that are not always necessary for anonymous users. Things like controls.css or forms.css and maybe even corev4.css in some cases may not be needed. Therefore, I decided to approach the problem with the same mindset already adopted for the additional JavaScripts which made some sense to be able to have a control which would be able to strip out unnecessary or superfluous JavaScript and CSS files. In addition to Chris O'Brien's code example, here's mine:
In the example above I haven't included Chris's code or class-level variables to make it slightly easier to read. Suffice to say that I've added his code into a separate method which is called from the OnPreRender event (see the commented out this.RemoveScripts(); line). I've also changed the OnInit slightly to check for empty strings prior to attempting to extract the entered value into the internal List variable.
One thing I noticed was that there are several CSS files also loaded by SharePoint that are not always necessary for anonymous users. Things like controls.css or forms.css and maybe even corev4.css in some cases may not be needed. Therefore, I decided to approach the problem with the same mindset already adopted for the additional JavaScripts which made some sense to be able to have a control which would be able to strip out unnecessary or superfluous JavaScript and CSS files. In addition to Chris O'Brien's code example, here's mine:
private List _cssFiles = new List();
private List _cssFileIndicesToBeRemoved = new List();
private List _foundCssFiles = new List();
public string CSSFiles { get; set; }
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
// store the css files property into the class private List variables
if (String.IsNullOrEmpty(this.CSSFiles) == false)
{
string[] css = this.CSSFiles.Split(new char[] { ';' });
this._cssFiles.AddRange(css);
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
// remove files but only for anonymous users
if (HttpContext.Current.User.Identity.IsAuthenticated == false)
{
// this.RemoveScripts();
this.RemoveCSS();
}
}
private void RemoveCSS()
{
int index = 0;
for (int iteration = 0; iteration < 2; iteration++)
{
IList files = null;
FieldInfo prop = null;
this._cssFileIndicesToBeRemoved = new List();
if (iteration == 0)
prop = this.GetFieldInfo(SPContext.Current, "m_css");
else
prop = this.GetFieldInfo(SPContext.Current, "m_orderedCss");
if (prop != null)
{
files = (IList)prop.GetValue(SPContext.Current);
foreach (var file in files)
{
FieldInfo cssRegisterProp = this.GetFieldInfo(file, "CssReference");
if (cssRegisterProp != null)
{
string cssFileName = cssRegisterProp.GetValue(file).ToString();
// add the file to the list of indices for removal
if (string.IsNullOrEmpty(this._cssFiles.Find(delegate(string sFound)
{
return cssFileName.ToLower().Contains(sFound.ToLower());
})) == false)
{
this._cssFileIndicesToBeRemoved.Add(index);
}
else
{
#if DEBUG
this._foundCssFiles.Add(cssFileName);
#endif
}
}
index++;
}
// reset the index counter and remove the required files
index = 0;
foreach (int j in this._cssFileIndicesToBeRemoved)
{
files.RemoveAt(j - index);
index++;
}
prop.SetValue(SPContext.Current, files);
}
}
}
private FieldInfo GetFieldInfo(object Type, string FieldName)
{
Type t = Type.GetType();
FieldInfo prop = t.GetField(FieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
return prop;
}
To explain a little. The CSS files are "registered" using the CSSRegistration control. When a CSS file is registered it gets stored in a CSSReferences property of the current SPContext which is an internal property of type ICollection<CssRegistrationRecord>. This property is read-only which means that while we can look at the collection we cannot write any changes back through this property. Fortunately, by looking at the Get for this property, we can see that it uses two private variables - m_css and m_orderedCss - to work out the ICollection contents. By accessing these two variables directly and changing them using reflection we can trim out the unnecessary files in the same way that we can trim out unwanted JavaScript files for anonymous users.In the example above I haven't included Chris's code or class-level variables to make it slightly easier to read. Suffice to say that I've added his code into a separate method which is called from the OnPreRender event (see the commented out this.RemoveScripts(); line). I've also changed the OnInit slightly to check for empty strings prior to attempting to extract the entered value into the internal List variable.
Labels:
CSS,
JavaScript,
SharePoint 2010,
SharePoint 2010 Performance
Thursday, 7 April 2011
UI Design and Taxonomy
Sometimes, when you're designing a UI for a better experience for Users, you want to use some of the functionality usually reserved for content authors.
What do I mean? Well, normally in Publishing Sites, when you're in edit mode you expose components and UI to content authors and allow a great user experience. The page becomes published and then that same data might be shown to readers of the page as simple text or perhaps formatted in some HTML - either way, it's exposed as static data. But suppose you have a form which readers need to interact with to, say, select a set of search criteria which you have available.
One such part of SharePoint 2010 which is likely to be used frequently is Managed Metadata and taxonomy. It may be you want to allow Users to select a Term from your Term Set in search rather than allowing a manual text input field. Fortunately, this is something someone has thought about at Microsoft and therefore created the Taxonomy Web Tagging Control.
First of all, a little about why a separate control is necessary.
In Publishing, due to the way Page Layouts are closely tied in with Content Types Fields are exposed using the Field Controls which, obviously, use the underlying Field from the Content Type to store and retrieve it's data. Outside of a Page Layout the Field Controls in general fall down as they rely on this close relationship for their supply and storage and therefore cannot be used unless the underlying item has a Field within its Content Type that matches.
Taxonomy Field Controls are no different, so therefore the Taxonomy Web Tagging Control steps in to allow coupling to the Term Store and Term Set directly, bypassing the need for a Content Type and freeing up the control to be used just about anywhere (see the hyperlink below for more information about this component).
Two slightly awkward bits of information are required: the Term Store ID (exposed as the SSPList property) and the Term Set ID (exposed as the TermSetList property). How do you get these?
Open up Central Admin and go to your Managed Metadata service - you'll see the Term Store ID exposed in the URL. If you navigate the Term Store to the Term Set you want to use and open up something like the IE Dev Toolbar you can step through the HTML structure to the LI tag of the Term Set and the ID attribute of the LI tag is your Term Set ID.
Here's some more information from Microsoft on the Web Tagging Control and how to use it: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.taxonomy.taxonomywebtaggingcontrol.aspx
What do I mean? Well, normally in Publishing Sites, when you're in edit mode you expose components and UI to content authors and allow a great user experience. The page becomes published and then that same data might be shown to readers of the page as simple text or perhaps formatted in some HTML - either way, it's exposed as static data. But suppose you have a form which readers need to interact with to, say, select a set of search criteria which you have available.
One such part of SharePoint 2010 which is likely to be used frequently is Managed Metadata and taxonomy. It may be you want to allow Users to select a Term from your Term Set in search rather than allowing a manual text input field. Fortunately, this is something someone has thought about at Microsoft and therefore created the Taxonomy Web Tagging Control.
First of all, a little about why a separate control is necessary.
In Publishing, due to the way Page Layouts are closely tied in with Content Types Fields are exposed using the Field Controls which, obviously, use the underlying Field from the Content Type to store and retrieve it's data. Outside of a Page Layout the Field Controls in general fall down as they rely on this close relationship for their supply and storage and therefore cannot be used unless the underlying item has a Field within its Content Type that matches.
Taxonomy Field Controls are no different, so therefore the Taxonomy Web Tagging Control steps in to allow coupling to the Term Store and Term Set directly, bypassing the need for a Content Type and freeing up the control to be used just about anywhere (see the hyperlink below for more information about this component).
Two slightly awkward bits of information are required: the Term Store ID (exposed as the SSPList property) and the Term Set ID (exposed as the TermSetList property). How do you get these?
Open up Central Admin and go to your Managed Metadata service - you'll see the Term Store ID exposed in the URL. If you navigate the Term Store to the Term Set you want to use and open up something like the IE Dev Toolbar you can step through the HTML structure to the LI tag of the Term Set and the ID attribute of the LI tag is your Term Set ID.
Here's some more information from Microsoft on the Web Tagging Control and how to use it: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.taxonomy.taxonomywebtaggingcontrol.aspx
Subscribe to:
Posts (Atom)