Archive for the ‘SharePoint’ Category

Posted: March 9, 2012 in .NET, SharePoint

Thank you W0ut. This saved me some time.

Advertisements

In this example I am using –

  • SharePoint MOSS 2007
  • JQuery 1.5.2 min

 A colleague asked me for a way to spruce up his SharePoint survey page with images, so I put together the following script. It is generic and re-usable so you might even wrap it into a js file to share between sites.

Check out the CSS tutorials here  and the selector tutorial on the JQuery site  to learn more how the selectors work.

Examples –
$(‘body) selects the body element
$(‘.ms-formbodysurvey span.ms-RadioText’) selects all span elements with the class ‘ms-RadioText’ that are a descendent of an element with the class ‘ms-formbodysurvey’.

I’ve added the comments inside the code to explain what is happening. You’ll need to update the paths to your JQuery library, option text, and image paths. You can use the ‘ToolPaneView=2’ hack to put your NewForm.aspx page into edit mode. Then add a ‘Content Editor’ webpart to place the following code in.

Also, you might want to add a bit of CSS to style the position, margin, or padding of the images.

<!-- Including the JQuery library in the page.      This provides the element selector function $() --> 
<script type="text/javascript" src="/sites/scripts/jquery-1.5.2.min.js" ></script>
<script type="text/javascript">
$(document).ready(function() {
  // Handler for .ready() called when the document is loaded.
  // We call the following function to iterate through each option
  IterateThroughOptions();
});

function IterateThroughOptions(){
  // This selects all elements that match the expression 
  // '.each' performs the defined function on each of the matched elements 
  $('.ms-formbodysurvey span.ms-RadioText').each(function(){
      // The span element contains an attribute called Title that we store in a variable called optionText 
      // This is the text that is displayed next to the radio button 
      var optionText = $(this).attr('Title');
      // Pass the optionText value to our function 'GetImgUrl'
      var imgUrl = GetImgUrl(optionText);
      // Append a new element (img) to our span element 
      $(this).append('<img src="' + imgUrl + '" />');
    });
}
function GetImgUrl(optionText){
  // Check the option text and return the correct URL 
  if(optionText === "Chargers")
    { return "/sites/NFL_Images/Chargers.jpg"; }
  if(optionText === "Cowboys")
    { return "/sites/NFL_Images/Cowboys.jpg"; }
  if(optionText === "Dolphins")
    { return "/sites/NFL_Images/Dolphins.jpg"; }
// Add more 'IF' statements to match the rest of the option text 
// If there are no matches, use this default image path  
return "/sites/NFL_Images/helmet50.gif";
}
</script>

I recently needed to move a MOSS 2007 wiki into a MOSS 2010 wiki. There’s no easy OOTB way to do this and third party tools are expensive, so I am rolling my own apps to handle an export of wiki content from MOSS 2007 and another app to do the import to MOSS 2010.

This application –

  • Filters a directory of files based on a provided extension
  • Parses the content of each file
  • Creates a new wiki document with the same name as the file
  • Inserts the parsed content into the new wiki file.

Refer to this article to exporting a wiki from SharePoint 2007
https://jeremybranham.wordpress.com/2010/12/20/export-a-sharepoint-2007-wiki-to-the-filesystem/

I used these 2 articles as a reference to build this app.
http://www.zimmergren.net/archive/2009/11/30/sp-2010-getting-started-with-the-client-object-model-in-sharepoint-2010.aspx
http://msdn.microsoft.com/en-us/library/ee857094.aspx

First, you need the correct client DLLs to access the SharePoint Client Object Model. If you don’t have MOSS 2010 installed on your development machine, you can download the correct files here.
http://www.microsoft.com/downloads/en/details.aspx?FamilyID=b4579045-b183-4ed4-bf61-dc2f0deabe47

After you’ve installed the client Object Model Libraries, make a reference to them in your project and you will be on your way to building a client app.

I ran into a couple of ‘gotchas’ while puttting this one together. Here is a segment of code that gave me some trouble.


using (SP.ClientContext ctx = new SP.ClientContext(txtURL.Text))
{
//* Add page to library
SP.Web web = ctx.Web;
var list = web.Lists.GetByTitle(cmbLists.SelectedItem.ToString());
ctx.Load(list);
SP.Folder rootFolder = list.RootFolder;
string serverRelativeUrl = web.ServerRelativeUrl + "/" + list.Title + "/" + "AnotherTestFromOM" + ".aspx";
SP.File wikiPage = rootFolder.Files.AddTemplateFile(serverRelativeUrl, SP.TemplateFileType.WikiPage);
SP.ListItem wikiItem = wikiPage.ListItemAllFields;
wikiItem["WikiField"] = "<p>This is a test</p>";
ctx.ExecuteQuery();
}
  • web.ServerRelativeUrl = ‘web.ServerRelativeUrl’ threw an exception of type ‘Microsoft.SharePoint.Client.PropertyOrFieldNotInitializedException’
    • I found that a call to ExecuteQuery() is required before access to certain properties are available.
  • Column ‘WikiField’ does not exist
    • For some reason the WikiField didnt exist in the ‘Pages’ enterprise wiki library but it worked in another wiki library that I created.

 

Here is the corrected segment –
     

        private void CreateNewWikiDoc(string fileName, string content)
        {
            using (SP.ClientContext ctx = new SP.ClientContext(txtURL.Text))
            {
                SP.Web web = ctx.Web;
                var list = web.Lists.GetByTitle(cmbLists.SelectedValue.ToString());
                SP.Folder rootFolder = list.RootFolder;
                ctx.Load(list);
                ctx.Load(rootFolder);
                ctx.ExecuteQuery();
                string serverRelativeUrl = rootFolder.ServerRelativeUrl + "/" + fileName + ".aspx";
                SP.File wikiPage = rootFolder.Files.AddTemplateFile(serverRelativeUrl, SP.TemplateFileType.WikiPage);
                SP.ListItem wikiItem = wikiPage.ListItemAllFields;
                wikiItem["WikiField"] = content;
                wikiItem.Update();
                ctx.ExecuteQuery();
            }
        }

 

Here is the complete backend. When I can clean this up a bit, I’ll try to put something in codeplex if there’s not already something there like it.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.SharePoint;
using SP = Microsoft.SharePoint.Client;
using System.IO;

namespace MOSS2010_Wiki_Import
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void btnBrowse_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.Forms.FolderBrowserDialog dlg = new System.Windows.Forms.FolderBrowserDialog();

            // Show open file dialog box
            dlg.ShowDialog();

            // Process open file dialog box results
            if (!string.IsNullOrEmpty(dlg.SelectedPath))
            {
                txtSource.Text = dlg.SelectedPath;
            }
        }

        private void btnGo_Click(object sender, RoutedEventArgs e)
        {
            if (!ParametersValidated())
                return;
            try
            {
                ParseFiles();
            }
            catch (Exception ex)
            {
                txtOutput.Text = ex.Message;
            }
        }

        void ParseFiles()
        {

            // Put all files that match the selected extension in root directory into array.
            string[] fileArray = Directory.GetFiles(txtSource.Text, "*." + cmbExt.Text); // <-- Case-insensitive

            //Parse the file list
            foreach (string fileNameAndPath in fileArray)
            {
                // Get the contents of the file
                string content = GetFileContent(fileNameAndPath);

                // Call the method that creates the new file.
                CreateNewWikiDoc(RemoveExtAndPath(fileNameAndPath), content);
            }
        }

        private string RemoveExtAndPath(string fileNameAndPath)
        {
            string[] pathSegments;
            pathSegments = fileNameAndPath.Split('\\');
            return pathSegments.Last().Replace("." + cmbExt.Text, "");
        }

        private string GetFileContent(string fileName)
        {
            TextReader tr = new StreamReader(fileName);
            return tr.ReadToEnd();
        }

        private void CreateNewWikiDoc(string fileName, string content)
        {
            using (SP.ClientContext ctx = new SP.ClientContext(txtURL.Text))
            {
                SP.Web web = ctx.Web;
                var list = web.Lists.GetByTitle(cmbLists.SelectedValue.ToString());
                SP.Folder rootFolder = list.RootFolder;
                ctx.Load(list);
                ctx.Load(rootFolder);
                ctx.ExecuteQuery();
                string serverRelativeUrl = rootFolder.ServerRelativeUrl + "/" + fileName + ".aspx";
                SP.File wikiPage = rootFolder.Files.AddTemplateFile(serverRelativeUrl, SP.TemplateFileType.WikiPage);
                SP.ListItem wikiItem = wikiPage.ListItemAllFields;
                wikiItem["WikiField"] = content;
                wikiItem.Update();
                ctx.ExecuteQuery();
            }
        }

        /// <summary>
        /// Todo: Insert Validation
        /// </summary>
        /// <returns></returns>
        private bool ParametersValidated()
        {
            return true;
        }

        private void btnGetLists_Click(object sender, RoutedEventArgs e)
        {
            cmbLists.Items.Clear();
            using (SP.ClientContext ctx = new SP.ClientContext(txtURL.Text))
            {
                var web = ctx.Web;

                ctx.Load(web);
                ctx.Load(web.Lists);

                ctx.ExecuteQuery();

                foreach (SP.List list in web.Lists)
                {
                    if (list.Hidden)
                        continue;
                    if (list.BaseType == SP.BaseType.DocumentLibrary)
                    cmbLists.Items.Add(list.Title);
                }
            } 
        }
    }
}

*Updated* – A codeplex project has been created for this. http://sp2007wikiexport.codeplex.com/

I recently needed to move a SharePoint 2007 wiki into a SharePoint 2010 wiki library. After some digging, I found no easy way to do this.

So I’ve decided to write a couple client apps – one that can export the 2007 wiki to the filesystem, then another to import the files into the 2010 wiki library (I figure the import app might also come in handy in the future).

Here is the UI  –

The result is a folder of html/aspx/(or whatever extension you provide) files that can be parsed further in some other process.

Some code reference from these sites were used.
http://sqlblogcasts.com/blogs/drjohn/archive/2007/11/02/Getting-a-list-of-files-from-a-moss-document-library-using-a-SharePoint-web-service.aspx

http://blogs.msdn.com/b/arpans/archive/2007/07/24/sharepoint-web-service-example-grabbing-wiki-content.aspx

Here’s the majority of the code. When I have time, I will create a codeplex project and clean this up a bit…


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
using System.IO;

namespace MOSS2007_Wiki_Export
{
///
/// Interaction logic for MainWindow.xaml
///
public partial class MainWindow : Window
{
string siteUrl;
string documentLibraryName;
string rowLimit = “10000”;
string wikiBody;
string wikiPageName;
string htmlBody;
string extention;
string beforeText;
string afterText;

public MainWindow()
{
InitializeComponent();
}

private void btnGo_Click(object sender, RoutedEventArgs e)
{
if(ParametersSet())
DoIt();

}

private bool ParametersSet()
{
siteUrl = txtURL.Text;
documentLibraryName = txtLibraryName.Text;
int limit;
if (int.TryParse(txtRowLimit.Text, out limit))
{
rowLimit = limit.ToString();
}
extention = cmbExt.SelectedValue.ToString();
beforeText = txtBefore.Text;
afterText = txtAfter.Text;
if (!DestinationFolderSet())
{
txtOutput.Text += “Please set the destination folder.”;
return false;
}
return true;
}

private bool DestinationFolderSet()
{
if (string.IsNullOrEmpty(txtDestination.Text))
return false;
return true;
}

private void DoIt()
{
StringBuilder updates = new StringBuilder();

SharePointLists.Lists wsList = new SharePointLists.Lists();
wsList.Credentials = System.Net.CredentialCache.DefaultCredentials;
wsList.Url = siteUrl + @”/_vti_bin/lists.asmx”;

// get a list of all top level lists
XmlNode allLists = wsList.GetListCollection();
// load into an XML document so we can use XPath to query content
XmlDocument allListsDoc = new XmlDocument();
allListsDoc.LoadXml(allLists.OuterXml);
// allListsDoc.Save(@”c:\allListsDoc.xml”); // for debug
XmlNamespaceManager ns = new XmlNamespaceManager(allListsDoc.NameTable);
ns.AddNamespace(“d”, allLists.NamespaceURI);

// now get the GUID of the document library we are looking for
XmlNode dlNode = allListsDoc.SelectSingleNode(“/d:Lists/d:List[@Title='” + documentLibraryName + “‘]”, ns);
if (dlNode == null)
{
updates.AppendLine(“Document Library ” + documentLibraryName + ” not found!”);
txtOutput.Text = updates.ToString();
}
else
{
// obtain the GUID for the document library and the webID
string documentLibraryGUID = dlNode.Attributes[“ID”].Value;
string webId = dlNode.Attributes[“WebId”].Value;

updates.AppendLine(“Opening folder ” + documentLibraryName + ” GUID=” + documentLibraryGUID);
txtOutput.Text = updates.ToString();

// create ViewFields CAML
XmlDocument viewFieldsDoc = new XmlDocument();
XmlNode ViewFields = AddXmlElement(viewFieldsDoc, “ViewFields”, “”);
AddFieldRef(ViewFields, “GUID”);
AddFieldRef(ViewFields, “ContentType”);
AddFieldRef(ViewFields, “BaseName”);
AddFieldRef(ViewFields, “Modified”);
AddFieldRef(ViewFields, “EncodedAbsUrl”);
AddFieldRef(ViewFields, “WikiField”);
AddFieldRef(ViewFields, “LinkFilename”);
//viewFieldsDoc.Save(@”c:\viewFields.xml”); // for debug

// create QueryOptions CAML
XmlDocument queryOptionsDoc = new XmlDocument();
XmlNode QueryOptions = AddXmlElement(queryOptionsDoc, “QueryOptions”, “”);
AddXmlElement(QueryOptions, “Folder”, documentLibraryName);
AddXmlElement(QueryOptions, “IncludeMandatoryColumns”, “FALSE”);
// this element is the key to getting the full recusive list
XmlNode node = AddXmlElement(QueryOptions, “ViewAttributes”, “”);
AddXmlAttribute(node, “Scope”, “Recursive”);
queryOptionsDoc.Save(@”c:\queryOptions.xml”); // for debug

//Create Query CAML
XmlDocument queryDoc = new XmlDocument();
XmlNode Query = AddXmlElement(queryDoc, “Query”, “”);

// obtain the list of items in the document library
XmlNode listContent = wsList.GetListItems(documentLibraryGUID, null, Query, ViewFields, rowLimit, QueryOptions, webId);

XmlDocument xmlResultsDoc = new XmlDocument();
xmlResultsDoc.LoadXml(listContent.OuterXml);
ns = new XmlNamespaceManager(xmlResultsDoc.NameTable);
ns.AddNamespace(“z”, “#RowsetSchema”);
// xmlResultsDoc.Save(@”c:\listContent.xml”); // for debug

XmlNodeList rows = xmlResultsDoc.SelectNodes(“//z:row”, ns);
if (rows.Count == 0)
{
updates.AppendLine(“No content found”);
txtOutput.Text = updates.ToString();

}
foreach (XmlNode row in rows)
{
htmlBody = beforeText;

wikiPageName = txtDestination.Text + “\\” + row.Attributes.GetNamedItem(“ows_LinkFilename”).Value.Replace(“.aspx”, “.html”);
wikiBody = row.Attributes.GetNamedItem(“ows_WikiField”).Value;

htmlBody += wikiBody;
htmlBody += afterText;
WriteFile(wikiPageName, wikiBody);

}
}
updates.AppendLine(“Done”);
txtOutput.Text = updates.ToString();
}

private void WriteFile(string name, string body)
{
StreamWriter sw = new StreamWriter(name);
sw.WriteLine(htmlBody);
sw.Close();
}

public static XmlNode AddXmlElement(XmlNode parent, string elementName, string elementValue)
{
XmlNode element = parent.AppendChild(parent.OwnerDocument.CreateNode(XmlNodeType.Element, elementName, “”));
if (elementValue != “”)
element.InnerText = elementValue;
return (element);
}

public static XmlNode AddXmlElement(XmlDocument parent, string elementName, string elementValue)
{
XmlNode element = parent.AppendChild(parent.CreateNode(XmlNodeType.Element, elementName, “”));
if (elementValue != “”)
element.InnerText = elementValue;
return (element);
}

public static XmlNode AddXmlAttribute(XmlNode element, string attrName, string attrValue)
{
XmlNode attr = element.Attributes.Append((XmlAttribute)element.OwnerDocument.CreateNode(XmlNodeType.Attribute, attrName, “”));
if (attrValue != “”)
attr.Value = attrValue;
return (attr);
}

public static void AddFieldRef(XmlNode viewFields, string fieldName)
{
XmlNode fieldRef = AddXmlElement(viewFields, “FieldRef”, “”);
AddXmlAttribute(fieldRef, “Name”, fieldName);
}

private void btnBrowse_Click(object sender, RoutedEventArgs e)
{
System.Windows.Forms.FolderBrowserDialog dlg = new System.Windows.Forms.FolderBrowserDialog();

// Show open file dialog box
dlg.ShowDialog();

// Process open file dialog box results
if (!string.IsNullOrEmpty(dlg.SelectedPath))
{
// Open document
txtDestination.Text = dlg.SelectedPath;
}

}

}
}

Unfortunately, there is no ‘Anchor‘ support in the SharePoint 2007 wiki wysiwyg editor. If you want to continue using the ‘out of the box’ wysiwyg editor, you’ll need to add anchors in the source view.

To access the html source view, open the desired wiki page into edit mode.

Look for an icon on the editor toolbar that looks like a page with angle bracket symbols.

 

Once in the source view – look for the location where you would like to add an anchor. If you are not familiar with html this may be a little overwhelming (and I would suggest hitting the w3schools website for some basic html tutorials).

In this example, I am adding an anchor to the bottom of the wiki page called ‘bottom’.
<A id=bottom></A>

 

Click ‘ok’ to return to the wysiwyg editor.

We now need the url to the wiki page you are editing –
right click the wiki page in your breadcrumb and choose ‘Copy Shortcut’. Your breadcrumb may look different or be in a different location depending on your master page configuration.

 

Once you have the wiki page url copied, lets add a new hyperlink to your wiki document.

Now paste the URL into the hyperlink form and add ‘#bottom’ to the end of the address(or whatever you named the anchor on your page)

You should now have a hyperlink in your wiki that points to an anchor! (Wouldn’t this be easier if the wysiwyg editor supported anchors?)

Toy Car

This is an example how to use the SPServices JQuery library to filter a SharePoint hierarchical drop down list (cascading lookup column) on an additional column.

For a better understanding, first read the documentation for the Cascading Drop Down List function of the SPServices.

http://spservices.codeplex.com/wikipage?title=%24().SPServices.SPCascadeDropdowns&referringTitle=Documentation

In this example, I am using –

I recently created a ‘Multi Project Tracking’ site template to handle the tracking of multiple projects and the associated tasks and releases associated with the projects.
The highest level list is the ‘Projects’ list. The ‘Projects’ list has a title and a few other columns.

A ‘Release’ list was created to track future releases of a project and a roadmap could be realized. A status column was added (Open, Locked, Closed) to let the team members know if task level issues could be assigned to this release.

An ‘Issues’ list was created to track tasks related to the projects/releases. In the ‘Issues’ list, we need to know which project and release this task is being associated with so a lookup column was created to lookup the project and the release.
In an evironment where there are only a few projects with 1 or 2 releases each, this would be acceptable. In my case, there may be 30 projects actively being tracked with a constant closing and re-opening  of future projects. You can imagine the lookup column list could get very confusing, especially if a ‘release’ name for one project was the same as a ‘release’ name for another project.
That is where the cascading drop down list function saves the day. See the link at the top to understand how to implement the cascade (hierarchical) drop-down lists.

Now that we have a trimmed lookup column based on a hierarchy there is another issue we need to solve…
Let’s suppose you release a version of your project (tracked in the Release List) and want to lock the next version so no new issues can be assigned to it.
You can change the status to ‘Locked’ in the ‘Release’ List, but the users will still be able to choose the release when adding a new itme in the ‘Issues’ list. The answer – add an additional clause to the CAML Query in the cascading drop down list function.

By adding an additional clause in the CAML query, we can filter out the items that do not have an ‘Open’ status.
This code should look familiar if you’ve already implemented the cascading drop down lists.

 <script language="javascript" type="text/javascript">
 $(document).ready(function() {
  $().SPServices.SPCascadeDropdowns({
   relationshipList: "Release",
   relationshipListParentColumn: "ProjectID",
   relationshipListChildColumn: "Title",
   parentColumn: "ProjectID",
   childColumn: "Release",
   CAMLQuery: "<Eq><FieldRef Name='Status'/><Value Type='Text'>Open</Value></Eq>",
   debug: true
  });
 });
</script>

 

The following line is the key to filtering the data –

   CAMLQuery: "<Eq><FieldRef Name='Status'/><Value Type='Text'>Open</Value></Eq>", 

The CAMLQuery property allows you to create additional where clauses in the query.
In the example above, I am selecting all the records where the column ‘Status’ is equal to ‘Open’
Read more about the CAML Query schema here http://msdn.microsoft.com/en-us/library/ms467521.aspx

There is another tool that helps you build a CAMLQuery. I haven’t used it but it looks interesting… It would probably also help you learn the CAML syntax faster.
http://www.u2u.net/res/Tools/CamlQueryBuilder.aspx

Graphic which hints to Microsoft Windows

Image via Wikipedia

In this example, I am using –

First let me say, props to the SPServices developers. This is a great project and has become a necessity for my SharePoint development.

Goal – Require a unique title in a SharePoint list

This is helpful when you are using this list as a lookup value in another list or are using some other relational design.

*These steps may be slightly different depending on your configuration. This is a short and ‘to the point’ tutorial – let me know if there are some steps that would benefit from expansion.

  1. In SharePoint Designer, open the web site that contains the list you want to modify
  2. If you do not already have the JQuery and SPServices libraries installed, do this now.
    1. I usually create a ‘scripts’ folder in the root of the website, then create a ‘js’ folder, then upload my library files there.
  3. Next, find the list that you want to modify and expand it.
  4. Right click the “NewForm.aspx” and choose “New From Existing Page”
    1. This method automatically changes the list to use our new form
  5. In the source view of the Untitled page, find where you want to add the script tags.
    1. I usually add mine inside the main content placeholder.
  6. Paste the following  text inside your document –

    <!-- SPSERVICES JQUERY SCRIPT -->
    <script language="javascript" type="text/javascript" src="../../scripts/js/jquery.SPServices-0.5.7.min.js"></script>
    <script language="javascript" type="text/javascript" src="../../scripts/js/jquery-1.4.2.min.js"></script>
    <script language="javascript" type="text/javascript">
    $(document).ready(function() {
    $().SPServices.SPRequireUnique({
    columnStaticName: "Title",
    duplicateAction: 1,
    ignoreCase: "true",
    initMsg: "This value must be unique.",
    initMsgCSSClass: "ms-vb",
    errMsg: "This value is not unique.",
    errMsgCSSClass: "ms-formvalidation",
    completefunc: null
    });
    });
    </script>
  7. **Be Aware**
    1. You may need to correct the path of the JS libraries.
      <script language="javascript" type="text/javascript" src="~YOUR-PATH/jquery.SPServices-0.5.7.min.js"></script>
    2. You may not need these script references if your master page references them. This all depends on your current configuration.
  8. Save the file in your list as “MyNewForm.aspx”
  9. Repeat from step 4, for the “EditForm.aspx” so the title uniqueness can be maintained during an edit process.
  10. Now access the ‘New’ button on your list; validation should stop you now from creating a duplicate title.
  11. Review the $().SPServices.SPRequireUnique documentation here to discover all the parameters/options.