Archive for May 24, 2012

Word wrapping SharePoint list column headers

A customer for our Highlighter product recently asked us how you could modify SharePoints List View Web Part (LVWP) to word wrap column headers. He had found that as he had replaced lengthy text with status icons he could fit a lot more columns on the page – if only he could shrink the column headers down.

To be clear – this isn’t unique to lists using our Highlighter product, this example shows a basic SharePoint list with a long title that is causing a horizontal toolbar as it won’t word warp even though the column will only ever contain Yes or No.

Of course you could rename the column and shorted the name and put more information in the description – but that only appears on the Edit form so it’s a balancing act between being brief and giving enough info so everyone knows what the columns contains.

Anyway – how to make these columns word wrap?

Inspired by this post on Stack Exchange I looked at using Cascading Style Sheets to do this.

The property we need is white-space : normal

Then we need to figure out which parts of the html to apply it to. This is done by HTML classes, so taking into account SharePoint 2007 and 2010 and different column styles (number/text/filterable/non-filterable) we end up with.

So the white-space property is applied to html elements with a class of .ms-vh, ms-vh2-nograd and so on.

We could also make the column headers center aligned and red (just for an example) by putting in

So how do we add these styles to the page?

You can use SharePoint Designer, but perhaps the easiest way is to add it via a Content Editor Web Part (CEWP)

  • Go to your list.
  • Select Site Actions > Edit Page
  • Click “Add a web part”

SharePoint 2007

SharePoint 2010

  • Select Miscellaneous > Content Editor Web Part
  • Click “open tool pane” then “Source Editor”
  • Add in the CSS from above
  • Select Media and Content > Content Editor
  • Select “Click here to add new content”
  • On the ribbon select Html > Edit HTML Source
  • Add in the CSS.

And – word wrapping :-

If this doesn’t work for you then as with all things ‘code’ exact syntax is important so check everything carefully – a “ is not the same as a ” for example. Also be sure that you’ve put the CSS in the HTML Source area, not just directly into the rich text editor.

You can add lots more effects (Red, bold etc) but sometimes its hard figuring out exactly what html elements and classes to target (e.g. you can’t apply a colour to the .ms-vh table header, you’ve got to apply it to an anchor element inside this – so “.ms-vh a”) – Firebug, the IE developer tools or the Chrome equivalent are invaluable for this – they will save your sanity!

Think Small. Small Things Make A Big Difference in Employee Engagement

While there’s a natural tendency to want to “think big” in business, much effective management is actually the result of thinking small.

With complete respect for arguably the greatest advertising campaign ever (Doyle Dane Bernbach’s “Think Small” campaign for Volkswagen from the 1950s), being able to ‘think small’ has real applicability for how managers relate to their employees on a day-to-day basis.

Fact: Employee surveys consistently show that the single most important factor in employee engagement is an employee’s relationship with his or her direct manager.

No thoughtful business person would argue that big, visionary, strategic thinking is unimportant to an organization.  But the reality is such thinking is generally the province of an organization’s senior management.  In the managerial trenches, however, where the vast majority of managers reside, the emphasis is on keeping operations moving, deadlines met, costs contained, and ‘trains running on time.’  Thus, the nature of the manager-employee relationship is often shaped less by strategic matters than by the myriad of small, moment-to-moment interactions that ultimately determine how an employee feels about his or her manager… and therefore the organization.  This is the thread from which the cloth is made.

Given this context, here are five small, easy things managers can do – surprisingly often neglected – that can make a positive difference in a manager-employee relationship.

Return messages quickly – Simple and appreciated.  Ignoring employee messages, or waiting a long time to respond, conveys, “You’re issues aren’t important to me.”  I once worked with a very knowledgeable senior executive who routinely took weeks to return messages, which he would then do thoroughly and thoughtfully.  Of course by that time the original issue was either resolved, out of date or long forgotten.

Be on time for meetings – Similarly, chronic lateness sends the clear message, “My time is more important than yours.”  Early in my career I reported to a VP who was always at least 20 minutes late for her own staff meetings… and that time quickly turned into a gripe session in which her capable but frustrated staff spent the wasted time discussing the manger’s shortcomings – unnecessarily undermining an otherwise capable leader.

Express appreciation for a job well done.  The power of a sincere, well-timed thank you is significant.  Again, this gesture is small, free, obvious – and often neglected.

Take a genuine interest in your employees.  Learn some details about their lives outside of work.  Outside problems shape inside performance.  A small amount of honest interest concern will be appreciated and go a long way to building loyalty.

Be there.   As fundamental as it gets.  You can’t manage effectively if you’re not available.  Keep an open door.  Make yourself available for questions and problems  as much as reasonably possible.   While this may sound simple, the fact is, with numerous competing demands on a manager’s time, it’s easy to be distracted and irritated when questions arise.  It’s a mindset of availability.  Even a manager who’s on the road a lot but checks and returns messages promptly can effectively “be there” when physically distant.

One concluding note: Such conscientious treatment is in no way an abdication of managerial authority.  I always favored a considerate approach not because it was nice but because it was effective.   Management isn’t a dinner party.  In any organization, stuff needs to get done.  All the time.  On time.  Every manager needs authority.  But over the long term respect is a more powerful lever than fear. With a deadline looming and a project on the line, employees most readily give their all for a person they like and respect.

Safely cleaning HTML with strip_tags in C#

One of my favorites in the PHP libraries is the strip_tags function. Not only does it neatly remove HTML from an input it also allows you to specify which tags should stay. This is great if you are allowing your visitors to apply some basic HTML tags to their comments. This post explores two issues: using C# to remove unwanted tags, and cleaning up unwanted attributes that might be hidden in the allowed tags.

I wanted to clean some comments posted to a website from unwanted HTML tags. The users are allowed to or and even their posts but anything else must be stripped before it is posted to the site. I found several regular expressions for C# that allow you to strip HTML but these magically wipe all the HTML and leave nothing.

Below is the end result of of some hacking, and of course much love-hate with the regular expression library.

string StripTags(string Input, string[] AllowedTags)

The StripTags method takes an input string, and an array of allowed tags. It returns the input as a string, minus all not wanted tags.

string test1 = StripTags("

George

WBush", new string[]{"i","b"});
string test2 = StripTags("

George W Bush

", new string[]{"p"});
string test3 = StripTags("Martijn Dijksterhuis", new string[]{"a"});

Using the above example code returns the following:

GeorgeWBush

George W Bush

Martijn Dijksterhuis

string StripTagsAndAttributes(string Input, string[] AllowedTags)

The above StripTags function is similar to the original PHP strip_tags function in having the same weakness: It is still possible for a malicious user to insert attributes into each of the tags. Think “style=” and “id=”. We would be somewhat saver if we cleaned these as well. The StripTagsAndAttributes method does just that.

It first runs the input through StripTags, and for the remaining tags is strips out all but a restricted set of attributes.

string test4 = "Martijn Dijksterhuis";
Console.WriteLine(StripTagsAndAttributes(test4, new string[]{"a"}));

 

That “OnClick” attribute looks mighty unsafe. Running the above string throughStripTagsAndAttributes as in the example above returns:

This function probably needs some tuning if you want to allow, or restrict things even further.

A word of caution

Regular expressions are voodoo, very cool, but still voodoo. The above functions work for the tests I have applied to them, but your mileage may vary! If you have a special situation that doesn’t work leave a note below and maybe we can work out the problems.

Credits

The strip_tags function is of course inspired by the PHP version , and a Javascript implementation thereof by Kevin van Sonderveld. The attribute stripping routine is based on the regular expressions by mdw252 in one of the strip_tags manual page comments.

Source code

The complete source code for the StripTags function and StripTagsAndAttributesfunction with my test code can be found below:

using System;
using System.Text.RegularExpressions;

namespace StripHTML
{
class MainClass
{

private static string ReplaceFirst(string haystack, string needle, string replacement)
{
int pos = haystack.IndexOf(needle);
if (pos < 0) return haystack;
return haystack.Substring(0,pos) + replacement + haystack.Substring(pos+needle.Length);
}

private static string ReplaceAll(string haystack, string needle, string replacement)
{
int pos;
// Avoid a possible infinite loop
if (needle == replacement) return haystack;
while((pos = haystack.IndexOf(needle))>0)
haystack = haystack.Substring(0,pos) + replacement + haystack.Substring(pos+needle.Length);
return haystack;
}

public static string StripTags(string Input, string[] AllowedTags)
{
Regex StripHTMLExp = new Regex(@"(<\/?[^>]+>)");
string Output = Input;

foreach(Match Tag in StripHTMLExp.Matches(Input))
{
string HTMLTag = Tag.Value.ToLower();
bool IsAllowed = false;

foreach(string AllowedTag in AllowedTags)
{
int offset = -1;

// Determine if it is an allowed tag
// "" , " if (offset!=0) offset = HTMLTag.IndexOf('<'+AllowedTag+'>');
if (offset!=0) offset = HTMLTag.IndexOf('<'+AllowedTag+' ');
if (offset!=0) offset = HTMLTag.IndexOf("
// If it matched any of the above the tag is allowed
if (offset==0)
{
IsAllowed = true;
break;
}
}

// Remove tags that are not allowed
if (!IsAllowed) Output = ReplaceFirst(Output,Tag.Value,"");
}

return Output;
}

public static string StripTagsAndAttributes(string Input, string[] AllowedTags)
{
/* Remove all unwanted tags first */
string Output = StripTags(Input,AllowedTags);

/* Lambda functions */
MatchEvaluator HrefMatch = m => m.Groups[1].Value + "href..;,;.." + m.Groups[2].Value;
MatchEvaluator ClassMatch = m => m.Groups[1].Value + "class..;,;.." + m.Groups[2].Value;
MatchEvaluator UnsafeMatch = m => m.Groups[1].Value + m.Groups[4].Value;

/* Allow the "href" attribute */
Output = new Regex("()").Replace(Output,HrefMatch);

/* Allow the "class" attribute */
Output = new Regex("()").Replace(Output,ClassMatch);

/* Remove unsafe attributes in any of the remaining tags */
Output = new Regex(@"(<.*) .*=(\'|\""|\w)[\w|.|(|)]*(\'|\""|\w)(.*>)").Replace(Output,UnsafeMatch);

/* Return the allowed tags to their proper form */
Output = ReplaceAll(Output,"..;,;..", "=");

return Output;
}

public static void Main(string[] args)
{
string test1 = StripTags("

George

WBush", new string[]{"i","b"});
string test2 = StripTags("

George W Bush

", new string[]{"p"});
string test3 = StripTags("Martijn Dijksterhuis", new string[]{"a"});

Console.WriteLine(test1);
Console.WriteLine(test2);
Console.WriteLine(test3);

string test4 = "Martijn Dijksterhuis";
Console.WriteLine(StripTagsAndAttributes(test4, new string[]{"a"}));
}
}

Getting Started using the OData REST API to Query a SharePoint List

SharePoint 2010 exposes list data via OData.  I’m currently working on an article around SharePoint and OData.  As part of this effort, at various points, I’ll blog some of my intermediate samples.  This post details the minimum number of steps to query a SharePoint list using OData.

This post is one in a series on using the OData REST API to access SharePoint 2010 list data.

  1. Getting Started using the OData REST API to Query a SharePoint List
  2. Using the OData Rest API for CRUD Operations on a SharePoint List

By far, the easiest way to get started with SharePoint development is to use the 2010 Information Worker Demonstration and Evaluation Virtual Machine that runs using Hyper-V.  The instructions in this and future related posts will be for that virtual machine.  If you have setup your own development environment with a different machine name, it is easy enough to adjust these procedures to work with your own servers.

Procedure: Query a SharePoint list using OData

1.    Download, extract, and boot the virtual machine.  The download includes detailed instructions.

2.    Log into the virtual machine.  Use the username brads.  The password is pass@word1.

3.    Create a new list.  Name the list ‘Inventory’.  Create a two new columns: Description, and Cost.  Make the type of the Description column be ‘Single line of text’.  Make the type of the Cost column be ‘Currency’.  Enter a couple of rows of sample data.

After completing this task, my list looked like this:

 

4.    Start Visual Studio 2010 in the virtual machine.

5.    Create a new project.  Click File -> New -> Project.  Select a directory for the project.  Set the name of the project to Gears.  The New Project dialog box will look something like this:

 

6.    Right click on the References node in the Solution Explorer window, and click Add Service Reference.  Enterhttp://intranet/_vti_bin/listdata.svc for the address.  Change the namespace to Data.  The Add Service Reference dialog box will look like this:

 

Click OK.

7.    Copy and paste the following code into Program.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Gears.Data;
 
namespace Gears
{
    class Program
    {
        static void Main(string[] args)
        {
            TeamSiteDataContext dc =
                new TeamSiteDataContext(new Uri(“http://intranet/_vti_bin/listdata.svc”));
            dc.Credentials = CredentialCache.DefaultNetworkCredentials;
            var result = from d in dc.Inventory
                         select new
                         {
                             Title = d.Title,
                             Description = d.Description,
                             Cost = d.Cost,
                         };
            foreach (var d in result)
                Console.WriteLine(d);
        }
    }
}
 

8.    Press F5 to run.  If everything worked, you will see something like the following:

Using the OData Rest API for CRUD Operations on a SharePoint List

SharePoint 2010 exposes list data via OData.  This post contains four super-small code snippets that show how to Create, Read, Update, and Delete items in a SharePoint list using the OData Rest API.

This post is one in a series on using the OData REST API to access SharePoint 2010 list data.

  1. Getting Started using the OData REST API to Query a SharePoint List
  2. Using the OData Rest API for CRUD Operations on a SharePoint List

These snippets work as written with the 2010 Information Worker Demonstration and Evaluation Virtual Machine.  That VM is a great way to try out SharePoint 2010 development.  Also see How to Install and Activate the IW Demo/Evaluation Hyper-V Machine.

See the first post in this series, Getting Started using the OData REST API to Query a SharePoint List, for detailed instructions on how to build an application that uses OData to query a list.  These snippets use the list that I describe how to build in that post.

Query a List

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Gears.Data;
 
class Program
{
    static void Main(string[] args)
    {
        // query
        TeamSiteDataContext dc =
            new TeamSiteDataContext(new Uri(“http://intranet/_vti_bin/listdata.svc”));
        dc.Credentials = CredentialCache.DefaultNetworkCredentials;
        var result = from d in dc.Inventory
                     select new
                     {
                         Title = d.Title,
                         Description = d.Description,
                         Cost = d.Cost,
                     };
        foreach (var d in result)
            Console.WriteLine(d);
    }
}
 

Create an Item

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Gears.Data;
 
class Program
{
    static void Main(string[] args)
    {
        // create item
        TeamSiteDataContext dc =
            new TeamSiteDataContext(new Uri(“http://intranet/_vti_bin/listdata.svc”));
        dc.Credentials = CredentialCache.DefaultNetworkCredentials;
        InventoryItem newItem = new InventoryItem();
        newItem.Title = “Boat”;
        newItem.Description = “Little Yellow Boat”;
        newItem.Cost = 300;
        dc.AddToInventory(newItem);
        dc.SaveChanges();
    }
}
 

Update an Item

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Gears.Data;
 
class Program
{
    static void Main(string[] args)
    {
        // update item
        TeamSiteDataContext dc =
            new TeamSiteDataContext(new Uri(“http://intranet/_vti_bin/listdata.svc”));
        dc.Credentials = CredentialCache.DefaultNetworkCredentials;
        InventoryItem item = dc.Inventory
            .Where(i => i.Title == “Car”)
            .FirstOrDefault();
        item.Title = “Car”;
        item.Description = “Super Fast Car”;
        item.Cost = 500;
        dc.UpdateObject(item);
        dc.SaveChanges();
    }
}
 

Delete an Item

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Gears.Data;
 
class Program
{
    static void Main(string[] args)
    {
        // delete item
        TeamSiteDataContext dc =
            new TeamSiteDataContext(new Uri(“http://intranet/_vti_bin/listdata.svc”));
        dc.Credentials = CredentialCache.DefaultNetworkCredentials;
        InventoryItem item = dc.Inventory
            .Where(i => i.Title == “Car”)
            .FirstOrDefault();
        dc.DeleteObject(item);
        dc.SaveChanges();
    }
}