Sue Hernandez's SharePoint Blog

SharePoint and Related Stuff

Category Archives: Web Services

Branding SharePoint 2010 using JQuery

Well, it’s been a while since I’ve written – it’s hard to get out of the weeds sometimes.   This time I had a need to “brand” a SharePoint 2010 site by applying styles “at the last second.”  Now this methodology could be argued against – I’m not talking about writing your own CSS and applying it to the Master Page.  This is on-the-fly style application, making sure that it’s the last style applied.

Our first step is to figure out a way to store and retrieve these settings per site.  The way I did it was to create a lightweight class, inside of a Custom SharePoint 2010 Web Service, that gets persisted to the database.  Why a web service?  Well you can call a web service from your Master Page without having to add something custom coded, for instance using Visual Studio 2010.  You can actually call the web service using JQuery.  One thing to note with this persistance is that you should store the full URL to the site, and use that to store and retrieve the data.  I originally did it by GUID but found that GUIDs for SPWebs are not unique across separate Site Collections – usually due to a backup/restore or migration using a custom tool.

Writing a custom SharePoint Web Service for 2010 is beyond the scope of this blog post, but there are many good articles out there that will help you get started.  A couple of hints, though, when you write this web service for 2010:

  • Your project references need to include System.ServiceModel and System.ServiceModel.Web
  • Your Interface needs   [OperationContract] and [WebInvoke(UriTemplate = “/YourMethodName”, Method = “GET”, BodyStyle = WebMessageBodyStyle.WrappedRequest, ResponseFormat = WebMessageFormat.Json)]
  • Your instance needs [BasicHttpBindingServiceMetadataExchangeEndpointAttribute] and
     [AspNetCompatibilityRequirementsAttribute(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
  • Your .svc file needs the Factory to be Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHostFactory

Our next step of course is to modify the existing Master Page – v4.master to be exact.  However, it is always said that you should never modify the master, you should make a copy of it and use the copy.  However you want to do it, we’re simply going to add a few lines of HTML and a reference to a Javascript file, so it’s a very small footprint.

First off, in the Head section of the Master Page, add a script link to JQuery (you can put it in your Layouts directory under a custom sub-folder – deploy it along with the web service as a WSP package) and add a link to your custom Javascript file (also deployed to Layouts).  At the top-ish of the body section of the Master Page, right underneath <SharePoint:SPNoScript runat=”server” /> put a script tag as follows:

<script type="text/javascript">
<!--
var siteURL = _spPageContextInfo.webServerRelativeUrl;
if (siteURL == '/') {
 siteURL = '';
}

var date = new Date();
// -->
</script>

What this does is uses a SharePoint script variable to reference the web site’s relative URL.  We’ll need this when we ask for the Web Service.

Toward the bottom of the master page, right before the div that is above the developer dashboard, add in the following script:

<script type="text/javascript">
<!--
// Set up the page Styles
GetStyles();
// -->
</script>

Your GetStyles method will be an Ajax call using JQuery, calling your custom web service.   Use the lightweight class in Javascript basically just like you would in code – brandingObj.RibbonTextFont.FontColor – for example.  

function GetStyles() {
 // Branding
 try {
  $.ajax({
   async: false,
   url: siteURL + "/_vti_bin/Custom/YourService.svc/GetSiteBranding?Date=" + date,
   type: "GET",
   dataType: "json",
   processData: true,
   success: function (xData) {
    try {
     brandingObj = xData;
     SetStyles();
    }
    catch (ex1) {
     alert("Error: " + ex1);
    }
   },
   error: function (result, status, error) {
    alert(result.status);
    alert(result.statusText);
    alert(error);
   },
   contentType: "application/json; charset=\"utf-8\""
  });
 }
 catch (ex) {
  alert("Error: " + ex);
 }
}

And your SetStyles function is where you actually apply the styles.  Here are some examples of what you can brand, using hard-coded colors instead of using the JSON object:

// Ribbon Text
$('.ms-cui-ribbonTopBars').find('.ms-menu-a').css({ 'color': 'White' });

// Top Ribbon Banner:  NOTATION - it is not advisable to set this to a light color as the Ribbon Tabs wil not be visible
$('body #s4-ribbonrow').css({ 'background-color': 'Firebrick' });

// Background Banner - Background Color
$('.s4-title').css({ 'background-color': 'Brown' });

// Background Banner - Image
$('.s4-title').css({ 'background': 'url(http://yourUrlHere.com)' });

// Header Site Image (Small image next to Title)
$('img[name='onetidHeadbnnr0']').attr('src', 'http://yourUrlHere.com');
$('img[name='onetidHeadbnnr0']').css({ 'height': '32px' });
$('img[name='onetidHeadbnnr0']').css({ 'width': '32px' });

// Page Header Text (Title)
$('.s4-titletext H2').css({ 'color': 'White' });
$('.s4-titletext').css({ 'color': 'White' });
$('.s4-titletext A').css({ 'color': 'White' });

// Page description Text
$('.s4-pagedescription').css({ 'color': 'White' });

// Top Link Bar (Tab Bar)
$('.s4-tn').css({ 'background-color': 'Firebrick' });

// Unselected Tabs in Top Link Bar
$('.s4-tn').find('li.static').find('A').css({ 'color': 'White' });

// Selected Tab in Top Link Bar
$('.s4-toplinks').find('.s4-tn').find('A.selected').css({ 'color': 'White' });

// Selected Tab in Top Link Bar - Background
$('.s4-toplinks').find('.s4-tn').find('A.selected').css({ 'background-color': 'Firebrick' });

// I Like It and Notes
$('.ms-socialNotif-text').css({ 'color': 'White' });

// I Like It and Notes - Hidden
$('.ms-socialNotif-Container').hide();

// Search
$('.ms-sbrow').hide();

// Help
$('.s4-help').hide();

// Recycle Bin
$('.s4-rcycl').hide();

// Quick Launch Background
$('#s4-leftpanel-content').css({ 'background-color': 'Brown' });

// Quick Launch Header Text
$('.ms-quickLaunch').find('li.static > A').css({ 'color': 'Yellow' });

// Quick Launch Item Text
$('.ms-quickLaunch').find('li.static > ul').find('A').css({ 'color': 'White' });
$('.s4-rcycl').css({ 'color': 'White' });

// Web Part Header
$('.ms-WPTitle').css({ 'color': 'Firebrick' });
$('.ms-WPTitle').find('A').css({ 'color': 'Firebrick' });

// Web Part Header Background
$('.ms-WPTitle').css({ 'background-color': 'Yellow' });
$('.ms-WPTitle').css({ 'padding-left': '3px' });
$('.ms-WPHeaderTdMenu').css({ 'background-color': 'Yellow' });
$('.ms-WPHeaderTdSelection').css({ 'background-color': 'Yellow' });

// Web Part Title Row
$('.ms-vh-div').css({ 'color': 'White' });
$('.ms-viewheadertr').find('A').css({ 'color': 'White' });

// Web Part Title Row Background
$('.ms-viewheadertr').css({ 'background-color': 'Brown' });
$('.ms-vh').css({ 'background-color': 'Brown' });
$('.ms-vh2').css({ 'background-color': 'Brown' });

// Web Part Body
$('.ms-listviewtable').find('td.ms-vb2').css({ 'color': 'White' });
$('.ms-listviewtable').find('td.ms-vb2').find('A').css({ 'color': 'White' });
$('.ms-listviewtable').find('td.ms-vb-title').find('A').css({ 'color': 'White' });
$('.ms-listviewtable').find('td.ms-vb-user').find('A').css({ 'color': 'White' });

// Web Part Body Background
$('.ms-listviewtable').find('td.ms-vb2').css({ 'background-color': 'Brown' });
$('.ms-listviewtable').find('td.ms-vb2').css({ 'background-color': 'Brown' });
$('.ms-listviewtable').find('td.ms-vb-title').css({ 'background-color': 'Brown' });
$('.ms-listviewtable').find('td.ms-vb-user').css({ 'background-color': 'Brown' });
$('.ms-listviewtable').find('td.ms-vb-icon').css({ 'background-color': 'Brown' });
$('.ms-listviewtable').find('td.ms-vb-itmcbx').css({ 'background-color': 'Brown' });

Now don’t take the colors I put in here at face value – I was just picking colors out of a hat.  I.e. don’t take this as “good design” colors cause it’s not.

Also, another trick if you want to style or hide the “All Site Content” link, all you have to do is in the Master Page, find the component with an ID of “idNavLinkViewAllV4″.  Add an attribute in there such as CssClass=”customViewAllSiteContent” and then in your JQuery all you have to do is reference that class.

SharePoint 2010 Remote Metadata – Client Object Model and WS

Today I was playing with an Add-In to have an attachment go from Microsoft Outlook straight to SharePoint into a Document Library, but they have to specify the Metadata, and one of the metadata fields is of type “Managed Metadata”.  So I had to figure out how to remotely get the choices for the metadata fields – i.e. the terms in the term set that apply only to that column.

So it turns out that you can get the SchemaXml property off of a field in a List and that will return a lot of information to you, like what some of the key ID’s are.  Below is an example where I’m getting key information to be able to dynamically display a control for the metadata field in question:

ClientContext spCtx = new ClientContext(webURL);
Web remoteWeb = spCtx.Web;

List docLib = remoteWeb.Lists.GetByTitle(listName);
spCtx.Load(docLib);
spCtx.Load(docLib.Fields);
spCtx.ExecuteQuery();
Control lastControl = null;
foreach (Field field in docLib.Fields)
{
 int top = 5;
 if (lastControl != null)
 {
  top = lastControl.Top + lastControl.Height + 5;
 }
 if (!field.Hidden && !field.ReadOnlyField)
 {
  if (field.InternalName != "FileLeafRef" &&
   field.InternalName != "ContentType" &&
   field.Title != "Title")
  {
   // Title will be same as File Leaf Ref (Name)
   Label newLbl = new Label();
   newLbl.AutoSize = false;
   newLbl.Width = 200;
   newLbl.Top = top;
   newLbl.Left = 5;
   newLbl.Text = field.Title;
   pnlControls.Controls.Add(newLbl);
   switch (field.TypeDisplayName)
   {
    case "Managed Metadata":
     string xml = field.SchemaXml;
     XmlDocument xDoc = new XmlDocument();
     xDoc.LoadXml(xml);
     string termSetID = string.Empty;
     string anchorID = string.Empty;
     string sspID = string.Empty;
     XmlNode termSetIDNode = xDoc.SelectSingleNode("//Property/Value[../Name = 'TermSetId']");
     if (termSetIDNode != null)
     {
      termSetID = termSetIDNode.InnerText;
     }
     XmlNode anchorIDNode = xDoc.SelectSingleNode("//Property/Value[../Name = 'AnchorId']");
     if (anchorIDNode != null)
     {
      anchorID = anchorIDNode.InnerText;
     }
     XmlNode sspIDNode = xDoc.SelectSingleNode("//Property/Value[../Name = 'SspId']");
     if (sspIDNode != null)
     {
      sspID = sspIDNode.InnerText;
     }
     ManagedMetadataField mmdFld = new ManagedMetadataField(webURL, sspID, termSetID, anchorID);
     mmdFld.Left = 210;
     mmdFld.Top = top;
     mmdFld.Tag = field.InternalName;
     lastControl = mmdFld;
     pnlControls.Controls.Add(mmdFld);
     break;
    default:
     TextBox slineOText = new TextBox();
     slineOText.Width = 264;
     slineOText.Tag = field.InternalName;
     slineOText.Left = 210;
     slineOText.Top = top;
     lastControl = slineOText;
     pnlControls.Controls.Add(slineOText);
     break;
   }
  }
 }
}

Then, in the user control for the Managed Metadata Field type, I call on the TaxonomyClientService web service to get the terms in the term set.  The code is below, however I don’t know what the significance of the <timestamp> piece is yet.  Also, I don’t know if those @a9’s and @a45’s and such actually change names depending on ??

List<Term> relevantTerms = new List<Term>();
if (!string.IsNullOrEmpty(termSetID))
{
 taxonomyWS.Taxonomywebservice ws = new taxonomyWS.Taxonomywebservice();
 ws.Url = webURL + "/_vti_bin/TaxonomyClientService.asmx";
 // TODO
 ws.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
 string oldtimestamp = "<timeStamp>633992461437070000</timeStamp>";
 string clientVersion = "<version>3</version>";
 string termStoreIds = "<termStoreId>" + sspID + "</termStoreId>";
 string termSetIds = "<termSetId>" + termSetID + "</termSetId>";
 int lcidEnglish = System.Globalization.CultureInfo.GetCultureInfo("en-US").LCID;
 string timeStamp = string.Empty;
 string termSet = ws.GetTermSets(termStoreIds, termSetIds, lcidEnglish, oldtimestamp, clientVersion, out timeStamp);
 XmlDocument xDoc = new XmlDocument();
 xDoc.LoadXml(termSet);
 XmlNodeList relevantTermNodes = null;
 if (!string.IsNullOrEmpty(anchorID) && anchorID != "00000000-0000-0000-0000-000000000000")
 {
  XmlNode anchorNode = xDoc.SelectSingleNode("//T[@a9 = '" + anchorID + "']");
  if (anchorNode != null)
  {
   relevantTermNodes = xDoc.SelectNodes("//T[contains(TMS/TM/@a45,'" + anchorID + "')]");
  }
 }
 
 if(relevantTermNodes == null)
 {
  relevantTermNodes = xDoc.SelectNodes("//T");
 }
 foreach (XmlNode node in relevantTermNodes)
 {
  string id = string.Empty;
  string name = string.Empty;
  string path = string.Empty;
  XmlNode idNode = node.SelectSingleNode("@a9");
  if (idNode != null)
  {
   id = idNode.InnerText;
  }
  XmlNode pathNode = node.SelectSingleNode("TMS/TM/@a40");
  if (pathNode != null)
  {
   path = pathNode.InnerText;
  }
  XmlNodeList nameNodes = node.SelectNodes("LS/TL/@a32");
  bool first = true;
  List<string> extraNames = new List<string>();
  foreach (XmlNode nameNode in nameNodes)
  {
   if (nameNode != null)
   {
    if (first)
    {
     name = nameNode.InnerText;
    }
    else
    {
     extraNames.Add(nameNode.InnerText);
    }
   }
   first = false;
  }
  if (!string.IsNullOrEmpty(id) &&
   !string.IsNullOrEmpty(name))
  {
   Term newTerm = new Term(id, name, path);
   if (extraNames.Count > 0)
   {
    newTerm.AlternateTerms = extraNames;
   }
   relevantTerms.Add(newTerm);
  }
 }
}
relevantTerms.Sort();
return relevantTerms;

Note the use here of a custom class called Term and the fact that you can have Alternate Terms.

Finally, when I go to return the final selected value, you have to get the WssID from the hidden list called TaxonomyHiddenList.

public string GetSelectedValue()
{
 if(!string.IsNullOrEmpty(txtTermValue.Text))
 {
  foreach (Term term in terms)
  {
   if (term.TermName.ToLower() == txtTermValue.Text.ToLower())
   {
    // Found the right term
    return GetWssIDByTermID(term.TermID) + ";#" + term.TermName + "|" + term.TermID;
   }
  }
 }
 return null;
}
private string GetWssIDByTermID(string termID)
{
 int result = -1;
 try
 {
  ClientContext spCtx = new ClientContext(webURL);
  Web web = spCtx.Web;
  List hiddenList = web.Lists.GetByTitle("TaxonomyHiddenList");
  CamlQuery query = new CamlQuery();
  query.ViewXml = "<View><Query><Where><Eq><FieldRef Name='IdForTerm' /><Value Type='Text'>" + termID + "</Value></Eq></Where></Query></View>";
  ListItemCollection items = hiddenList.GetItems(query);
  spCtx.ExecuteQuery();
  if (items != null)
  {
   spCtx.Load(items);
   spCtx.ExecuteQuery();
   if (items.Count > 0)
   {
    result = Convert.ToInt32(items[0]["ID"]);
   }
  }
 }
 catch
 {
  result = -1;
 }
 return result.ToString();
}