Tuesday, November 27, 2012

Adding large numbers of users to a SharePoint group using PowerShell and DisplayName

I'm often asked to add a large number of people to a SharePoint group.  I'm never given the account names - instead, people often copy and paste from Outlook.  This is almost always in the following format:

Surname, Firstname; Surname, Firstname;

The same formatting is used in the SharePoint environment.  What I've done for small numbers of people is to use the standard SharePoint built-in web interface, but this is limited to 256 characters at a time, and is rather slow when working with more data than that.  So, I wrote two PowerShell scripts.  The first converts semicolon-seperated values into an entry on each line.

$x = ''
$x = Get-Content "SemicolonUsers.txt"
$x.replace("; ","`r`n") | out-File "Users.txt"

Quite simple, probably better ways of doing this, but it'll do for now.  Then, the other PowerShell script does the actual work, and outputs errors to Errors.log.


$SiteURL = "http://sharepoint/sitename"
$GroupName = "SharePoint Group Name"
$UserNames = "Users.txt"

function WriteOutputError($message)
{
    $host.UI.RawUI.ForegroundColor = "red"
    Write-Output($message)
    $message |Out-File Errors.log -Append
    $host.UI.RawUI.ForegroundColor = "white"
}

function ConvertUser($user)
{
$date = Get-Date -format T
    $search = New-Object DirectoryServices.DirectorySearcher([ADSI]“”)
    $search.filter = “(&(objectClass=user)(displayName=$user))”
    $results = $search.Findall()
    $count = $search.Findall().Count
    if($count -ne 1) {
        WriteOutputError('' + $date + ': ' + $count + ' results found for ' + $user + ': cannot add.')
    }
    else {
        foreach($result in $results)
        {
        $userEntry = $result.GetDirectoryEntry()
        ### We still have multiple inactive domains, so focus only on the current domain
   $accountName = "DOMAIN\" + $userEntry.sAMAccountName | Out-String
        #Write-Output($date + ': Adding ' + $accountName)
        WriteOutputError(Set-SPUser -Web $SiteURL -Group $GroupName -Identity $accountName)
        }
    }
}

function ConvertUsers
{
process
    {
        foreach($user In $_)
        {
            ConvertUser($user)
        }
    }
}

$date = Get-Date -format T
WriteOutputError('--------------------------------------')
WriteOutputError('' + $date + ": Starting new instance of AddUsers.")
WriteOutputError('' + $date + ": SiteURL: " + $SiteURL)
WriteOutputError('' + $date + ": GroupName: " + $GroupName)
WriteOutputError('--------------------------------------')

$host.UI.RawUI.ForegroundColor = "white"
Get-Content $UserNames | ConvertUsers 
$date = Get-Date -format T
WriteOutputError('--------------------------------------')
WriteOutputError('' + $date + ': Operation completed.')
WriteOutputError('--------------------------------------')
$host.UI.RawUI.ForegroundColor = "gray"

Wednesday, November 21, 2012

Hiding certain fields from document properties using jQuery

I have a document library that uses a few "hidden" fields during its various automated workflows.  However, for some of these fields, I can't mark them as hidden fields in the document library properties, possibly because they're related to a content type.  So I need to find another way to hide them from the user when viewing or editing document properties.

I found a solution on the blog SharePoint Sherpa - SharePoint 2007 – Hiding fields on NewForm.aspx and EditForm.aspx, the easy way - but wanted to use jQuery, so adapted it slightly.  Here is what I came up with.

1. Find the document library's DispForm.aspx and/or EditForm.aspx in the hidden Forms folder. This can be found in SharePoint Designer, selecting All Files for that site, and finding the Forms folder in the correct library.
2. Find the line:
<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">
3. Add the following code:


<script type="text/javascript">

_spBodyOnLoadFunctionNames.push("hideFields");

function hideFields() {

$('.ms-formlabel:contains("Field name 1")').parent().hide();
$('.ms-formlabel:contains("Field name 2")').parent().hide();
$('.ms-formlabel:contains("Field name 3")').parent().hide();
$('.ms-formlabel:contains("Document ID")').parent().hide();

}
</script>

4. Change "Field name 1" to the name of the first field you want to hide.  Do the same for the other lines, and add more lines as required.  (I've chosen to hide Document ID - it's in use throughout our SharePoint site, but I don't want the users to see it in this library)

I'm sure there are ways to further improve this, perhaps by putting the field names into an array, but this will do for now.

Wednesday, November 7, 2012

Removing a document/list web part from a page if the list is empty

I have a web part page which has a few document web parts - the web parts have filters applied so they only show relevant items to that particular user.  However, sometimes there are no items to show, and then the user is shown a box with nothing in it, apart from a typical SharePoint message about how there are no items.  This can look a bit messy.

I finally found a solution, posted by Mike Smith (of Mike Smith's Tech Training Notes), which uses JavaScript to remove the empty web part.  If I had spare time, I would rewrite this in jQuery (as everything else on my SharePoint environment is in jQuery), but it's perfectly functional as it is now.  Thanks Mike, you're a star!

Wednesday, October 31, 2012

Filtering large SharePointlist data in InfoPath

I have a site which consists of a large (30,000+) list of customer records.  In another list, I want to create an InfoPath 2010 form that contains a lookup to that customer list.  Of course, with such a large number of customer records, that means the performance is extremely poor at times, and forcing the user to browse through 30,000 customers is also extremely poor.  So I'd like to filter that lookup list.

The most obvious option is to filter client-side.  This would resolve the usability factor, but would still cause poor performance.  So the next option is to do something server-side.  However, using the InfoPath data connection wizard, there doesn't appear to be a way to filter a SharePoint list.  It's all or nothing.

I did a bit of digging around and remembered ListData.svc.  It's a byproduct of ADO.NET data services, so I made sure that was activated on my SharePoint server (it was), and then used this forum post as a guide - InfoPathDev: Query Sharepoint List - Partial Match.

Since my customer list is on a subsite, I had to use common sense to locate the actual list:

http://sharepoint/sitename/_vti_bin/ListData.svc/Customer

Shoving this into my browser showed the XML data (I'm using XML Tree, a Google Chrome extension), but of course, it's the full 30,000+ records and took a few seconds to display.  So I continued reading, and found the filter system query options on OData.org, so came up with this:

http://sharepoint/sitename/_vti_bin/ListData.svc/Customer?$filter=substringof(tolower('SearchQuery'),tolower(ColumnName)) eq true

I'm amazed at how well this works.  I've used tolower() to surround my search query and the column name - this forces a case insensitive search and doesn't seem to affect performance.

My next challenge is to make this work in InfoPath.  I have a text box, named CustomerSearchField, where the user can enter the search query, and when a Search button is hit, the REST URL for the data connection is updated.  The first rule for the button is "Change REST URL".  On the "Rule Details" screen, select the correct Data connection that you would have created earlier, and click the "fx" button next to REST Web Service URL.  In here, I used this:

concat("http://sharepoint/sitename/_vti_bin/ListData.svc/Customer?$filter=substringof('", CustomerSearchField, "',tolower(LongName)) eq true")

Don't forget the concat().  This caused me a lot of stress trying to get this working.

Finally, add another rule to the Search button - "Query for data", and then choose the same Data connection defined above.  There, working.

Wednesday, October 17, 2012

Redirecting after Workflow Task page

In SharePoint Designer, I created a workflow that contains multiple approval workflows.  In the emails that are sent out by the approval workflows, a link is contained that sends the user to a URL that allows the user to approve, reject, etc.  (I know this functionality is included in Outlook 2010, but this is not what the key users wanted)

The URL is called using the variable [%Current Task:Form_URN%], which works fine, but when Approve or Reject is clicked, it takes the user to the Workflow Tasks list, not the main site landing page as is required.

The solution I found here is to simply add a Source query string parameter to the address in the email.  For example:

[%Current Task:Form_URN%]&Source=http://sharepoint/sitename

This creates a URL similar to the below:

http://sharepoint/sitename/_layouts/WrkTaskIP.aspx?List=%7Bd5cdf49b%2Da735%2D4669%2D8ce3%2Dc26d17666941%7D&ID=83&Source=http://sharepoint/sitename

Clicking on Approve or Reject (or, indeed, any other options you've added to the task) will then take the user back to the Source page instead, enhancing the user experience.  In theory, adding &Source to any SharePoint URL will have the same effect, which is extremely useful.

Monday, September 24, 2012

Getting concurrent users from SharePoint's RequestUsage SQL view

I've been curious about how our SharePoint farm is faring.  One of the statistics that is interesting to me is concurrent users - the number of users that the service can provide at any one time.  There are various methodologies behind this, but the one I opted to use was to look at how many unique users are accessing the site in a ten minute period.  It's not a 100% accurate determination of concurrency, but I didn't want to purchase additional software to do some analytics, and the SharePoint web analytics just wasn't cutting it for me.

First, I enabled Usage and Health Reporting (from Central Administration / Monitoring / Configure usage and health data collection), with the important event being Page Requests.  Enabling this will create a new database, typically named WSS_Logging, and a few SQL views to perform selects on this data.

Next, I used the following SQL.  I wanted to grab data for a one-week period.  Here's what I came up with:

SELECT 
MIN([LogTime]) AS 'From', 
MAX([LogTime]) AS 'To', 
COUNT([LogTime]) AS 'Total', 
COUNT(DISTINCT [UserLogin]) AS 'Unique'
FROM [WSS_Logging].[dbo].[RequestUsage]
WHERE LogTime < '2012-09-24 00:00:00.000' 
AND LogTime > '2012-09-17 00:00:00.000'
AND UserLogin != 'DOMAIN\AppPool'
AND UserLogin != 'SHAREPOINT\system'
AND UserLogin != 'DOMAIN\spfarmacc'
AND UserLogin != 'DOMAIN\ServFarmAcc'
AND UserLogin NOT LIKE '0#%'
AND UserLogin != ''
AND BytesConsumed > 0
GROUP BY
DATEPART(YEAR, [LogTime]),
DATEPART(MONTH, [LogTime]),
DATEPART(DAY, [LogTime]),
DATEPART(HOUR, [LogTime]),
(DATEPART(MINUTE, [LogTime]) / 10)

I realise there are probably better ways of doing this, but I'm no SQL expert. This will show a From and To datetime, a Total count of all applicable page requests, and a Unique count of unique user accounts within that Total. Therefore, the Unique column shows how many concurrent users were active during the times shown in From and To.

Friday, September 14, 2012

Consistently Slow performance with Server Publishing site feature enabled

Back from holiday to find the SharePoint 2010 site performing slowly.  On the previous day, there was an issue with SQL Server Agent and this needed to be restarted, but this may be unrelated.

The problem I faced was that seemingly every site or page was being delivered as expected, only with an additional delay of 10-15 seconds each time, consistently.  After this 10-15 seconds, the page would load instantly.  There was no mention of any problem in the system or SharePoint logs, and the latter was showing the usual request completion times.  It just seemed like the request was being held for 10-15 seconds.

Two site collections were unaffected: MySites (which is out-of-the-box SharePoint 2010 stuff) and Central Administration.  These both operated as usual, with response times between 1-2 seconds.

I later discovered that sites that have not had the SharePoint Server Publishing site feature were not affected.  To confirm this, I created a new Blank Site, and the pages loaded instantly.  As soon as I enabled the SharePoint Server Publishing feature, the performance problems reoccured.  Deactivating the feature did not fix the problem, so it seemed that any site that ever had this feature activated was afflicted.

Following this line of investigation, I checked the existing sites that had SharePoint Server Publishing activated previously. This feature was now deactivated on all sites, including the root site.

Following guidance from our SharePoint support contractors, I tried to view the Site Collection Output Cache settings.  This gave me an error, saying that the list does not exist, and a quick Google suggested I deactivate and reactivate SharePoint Publishing Infrastructure site collection feature.

I left this for the day, planning to try this when I came in this morning.  But when I signed in, the site was operating perfectly again.  The Server Publishing site feature was, again, activated for all sites.  I've looked through the logs to see if anything changed overnight, but can't see any record of this.  No-one in the office, or any of our contractors, made any changes to the server farm.  So, this is unsolved.  We do need to find out why this happened so we know how to prevent or fix it in the future, but so far, no solution.

Friday, August 17, 2012

Finding the .NET 2.0 Configuration Tool

Just a quickie then - needed to add something to the GAC on a server that didn't have the .NET 2.0 SDK installed.  Didn't want to download 350Mb of the SDK just for a small tool - and found that most of the links for the .NET 2.0 SDK are actually broken, as if the entire thing has vanished from Microsoft's servers, or are hosted on other download sites.

Found a very useful blog post and tool on Aaron Stebner's WebLog - Details about setup for the .NET Framework 2.0 configuration tool, where he gives more information on this elusive configuration tool and even provides a handy little download and installer, clocking in at under 1Mb.  Much better.

The simple art of making Json work in Internet Explorer

A couple of projects I've been working on - both for my day job, and for my personal site - utilise ASP.NET MVC, jQuery and Json.  My prior development was using exclusively ASP.NET AJAX and, before that, plain old PHP, so when I discovered the three technologies above, I've not really understood why web development was ever any other way.

I primarily develop in Google Chrome - I used to use FireFox and Firebug, but found that Chrome's included Developer Tools are as good as - possibly better than - Firebug, particularly the Network monitor. The organisation I work in only supports Internet Explorer, but that would only affect the layout and styling, right?  Once I've got the page looking right, I wouldn't need to check the actual functionality when it comes to Json data.

I couldn't have been more wrong.  Although the site worked fine in Internet Explorer, posting data back to the database, and the updated information would even show in Chrome, IE just refused to show what had changed.  So I dusted off the F12 Developer Tools in Internet Explorer - extremely difficult to do after using Chrome's Development Tools - and started to diagnose.

The first step I took was to open the Json request in a new IE tab.  This wouldn't work.  Instead of showing the raw Json data as I'd expect, it would attempt to download a file.  I didn't want this, and for a while, thought this might be the cause of all my problems.

I did some research, and the ever-useful stackoverflow gave me the answer - I had to change every return Json command in my Controller to explicitly specify a MIME type.  I shouldn't have expected Internet Explorer to figure it out for itself, like Chrome does.  This link - IE9 JSON Data “do you want to open or save this file” - gave me the following answer:

return Json(someData, "text/html", System.Text.Encoding.UTF8,
                        JsonRequestBehavior.AllowGet);

Splendid - IE doesn't insist on downloading this file now, and will show the raw Json data instead.  But looking at the data returned, I didn't see an error that would cause the data displayed on my actual page to be out of date - I used a simple "return Json(DateTime.Now ... )" test and this showed the correct time, down to the second.

So I continued my search for a solution to my second problem, and found another post - Unexpected Caching of AJAX results in IE8 - that indicated Internet Explorer forcibly caches AJAX requests.  Although the request appears in the Network monitor, the data it uses is out of date.  Adding the following line to my jQuery code resolves everything:

$.ajaxSetup({
    cache: false
});

I'm glad I've managed to solve this issue, but I must admit, it's taken some of the fun out of developing.  Everything was going so smoothly with MVC, Json, etc., until I discovered how antiquated and difficult Internet Explorer still is to this day.  Why is it so strict with MIME types?  Why is it automatically caching dynamic data?  And, of course, why does it interpret CSS so differently to other browsers?

And I suppose the ultimate question (or answer) is, why do Microsoft have to run an advertising campaign to convince people to use a free browser that is, let's face it, still included in Windows by default?

Rant over.  Moving on.

Thursday, August 9, 2012

Getting the current user's domain + account name in MVC 3

Quick and easy one (now that I've found it out) - prior to using MVC 3, I was using User.Identity.Name to get the current user's name.  But since moving to MVC 3, that property was returning blank.

Discovered the correct way to do this is simply:

Environment.UserDomainName + "\\" + Environment.UserName

This will happily return DOMAIN\accountname.  Easy when you know how...

Thursday, August 2, 2012

Silverlight appears on top of pop-up menu

The SharePoint site I'm working on features a top navigation bar that drops down a submenu when the main menu buttons are hovered over.  There's nothing particularly clever going on there - it uses jQuery, and is tied in with the standard SharePoint navigation.

However, a few pages on the site have a Silverlight object at the top of the editable page - so, directly underneath the top navigation bar.  On these pages, the Silverlight object would appear to be always on top, so the pop-up element would always appear behind it.  This means that you couldn't navigate to other areas of the site.

I found a fix on the Silverlight forums that consisted of two parts:

  1. For the DIV that contains the Silverlight object, position: absolute is required.
  2. For the Silverlight object, an additional param is required: windowless should be set to true, like this:  <param name="Windowless" value="true"/>
Problem solved, like magic.

Friday, July 27, 2012

Triggering a workflow when expiry date is approaching

Out of the box, SharePoint allows workflows or tasks to begin when a certain date is reached. However, there's nothing that will trigger a workflow when a date is approaching.  For example, I have a list of contracts that have expiry dates attached in a custom column.  I want an expiry notification to fire three months before that date.

I found a solution here - Item expiration reminders in SharePoint using workflow - but for some reason, the workflow didn't work as expected, possibly due to the structure of my data.  So I've made some changes, trimmed it a little, and present it below.

Only one additional column had to be created - named "Previous due date", it's a date column in which the workflow stores the current expiry date.  It may even be possible to remove this column entirely, but I've left it in to make it easier to check the progress of the workflow.

What the workflow does is: it stores a workflow variable called Alert Date, which is simply three months before the Expiration Date.  If the Expiration Date is after today, it will set the new column, "Previous due date", to the current Expiration Date, and also sets a workflow variable, "Original Due Date", to the Expiration Date.  It will then carry out two actions in parellel -

  • Wait for the Alert Date to be reached, and then send an email
  • Wait for the Expiration Date in the item to change, and then stop the workflow.
Since the workflow runs on change or create, we don't want the same workflow running multiple times on the same item because the item has been changed, so the second action takes care of that.

Here's the actual workflow:


Different views inside a document set

I have a site which has a large list of Document Sets, one for each customer.  These document sets are grouped by their country.  However, when opening the document set, the contents are also grouped by country, because by default, a document set uses the same view, and it's not smart enough to realise the grouping is for the document set, not the contents.

Easy solution I've just discovered (thanks to this blog post) -

  1. Create a new view, based on the original view, but without the Grouping
  2. Open the Library Settings, and select the Data Set from under Content Types
  3. Select Document Set Settings.
  4. Scroll down until you find the Welcome Page View setting - you can change the view here.

Thursday, July 12, 2012

SharePoint Lookup with Picker

A site I'm working on has a very long lookup list.  SharePoint 2010, by standard, only allows a dropdown for this, and that's just insane when you have thousands of items to choose from.  So I found the iLove SharePoint Lookup Field with Picker 2010 which allows the user to use something similar to the people picker, but with lookup entries.  Extremely useful and works perfectly, except...

The site uses Word templates, and the templates use SharePoint properties.  I'm getting some strange behaviour when using the Lookup Field with Picker: namely it's not possible to change the lookup value from Word.  I only get a single option - "1" - and two blank options.  When selecting the blank option, the "1" option vanishes from the document properties.

I'm now trying to find a way to display a dialog when the document is first created in SharePoint that forces the user to pick a selection from the lookup, and then not allow that selection to change.

Friday, June 15, 2012

Getting started with the Facebook API

I'm developing a site that requires users to log in, but I've opted to only allow access from Facebook users for now.  I need the user to grant access for Facebook to give me access to their profile, and then I need to actually get their profile.  The Facebook API documentation is thorough, but still wouldn't work without a few experiments and changes, so I figured this might be useful for someone else.

The code will present a button for the user to Login to Facebook, which will request access from Facebook.  The user will get a window asking for permission.  This only occurs once per user, or if they're not logged into Facebook.

The code will then grab the user's Facebook profile details, such as their name.

Don't forget to change <MY APP ID> to the app id given to you by the Facebook Developer site when you create your app.

Javascript:
// Initialise the Facebook API - note that the API itself is loaded
        // further down in the code
        window.fbAsyncInit = function() {
          FB.init({
            appId      : '<MY APP ID>',
            status     : true,
            cookie     : true,
            xfbml      : true,
            oauth      : true,
          });
         
        // Subscribe to an event in the Facebook API - this event
        // will fire whenever a change in authorisation status occurs.
        // This will happen when the user's Facebook account is
        // connected (essentially, authorised), or if the access token
        // expires.
        FB.Event.subscribe('auth.statusChange', function(response) {
            if(response.status == "connected") {
                // Logging lots of stuff to the console - you won't need all of this
                console.log(response);
                // Facebook account connected successfully - can now do things
                // with the Facebook API. First we really need the access token.
                useraccesstoken = response.authResponse.accessToken;
                console.log(useraccesstoken);
                // I'm taking the userID but only because I'm keeping a record
                // of who's connected. You might not need this.
                fbuid = response.authResponse.userID;
                               
                // Let's get the user's Facebook profile information, by passing
                // the useraccesstoken to prove that we have access.
                FB.api('/me?access_token=' + useraccesstoken, function(response) {
                    // Log the user's Facebook profile data object to the console,
                    // using the useraccesstoken taken from the auth.statusChange
                    console.log(response);
                });

            } else {
                // If status changes to anything other than "connected",
                // it's assumed the connection has been lost or there is
                // an error.
                console.log("Facebook login lost.");
            }
          });

        };
        // Load the Facebook API
        (function(d){
           var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
           js = d.createElement('script'); js.id = id; js.async = true;
           js.src = "//connect.facebook.net/en_US/all.js";
           d.getElementsByTagName('head')[0].appendChild(js);
         }(document));
HTML:
<div id="fb-root"></div>
<div class="fb-login-button">Login with Facebook</div>

Friday, June 8, 2012

Why is Microsoft still using spacer img to pad out their elements?

I truly hope this practice ends by the time the next version of SharePoint is released...
<td class="ms-descriptiontext" height="10px" colspan="2">
<img width="1" height="10" alt="" src="/_layouts/images/blank.gif">
</td>

Thursday, June 7, 2012

Viewing all user alerts on a SharePoint site

I find the User Alerts page in SharePoint 2007 and 2010 (viewable at http://sitename/_layouts/sitesubs.aspx) absolutely horrible to work with.  I don't want to click through every single user to see what alerts they have.  I'm amazed that Microsoft have still yet to improve this.

In the meantime, there are other alternatives.  There is a paid software option by Bamboo Solutions which looks very nice (at http://store.bamboosolutions.com/ps-74-5-alerts-administrator.aspx).  Or if you, like me, can't justify the cost of this application for the sake of getting this information once in a blue moon (and don't want to exploit the Free Trial), there is an SQL alternative, as detailed on the Just Geeks blog post here: http://justgeeks.blogspot.co.uk/2009/05/using-sql-server-to-view-all-alerts-in.html.  Might not be as pretty as Bamboo Alerts Administrator but was perfectly fine for what I needed.

Using left navigation on a SharePoint Web Part Page

Standard Web Part Pages in SharePoint 2010, by default, don't have the left navigation bar visible, for some reason.  Not sure why yet.  But I found this post quite helpful for putting the navigation bar back.

http://blogs.technet.com/b/seanearp/archive/2011/03/09/how-to-create-a-sharepoint-2010-web-part-page-that-inherits-the-site-s-left-navigation.aspx

To automate the process a little, I added this line to my style sheet -

#s4-leftpanel { display:inherit; }

So all you need to do in SharePoint Designer is comment (or delete) the two content placeholders:

<asp:Content ContentPlaceHolderId="PlaceHolderNavSpacer" runat="server"></asp:Content>
<asp:Content ContentPlaceHolderId="PlaceHolderLeftNavBar" runat="server"></asp:Content>

Friday, June 1, 2012

Navigation dropdowns in SharePoint 2010 using jQuery

I've been looking for a way to create dynamic top navigation dropdowns in SharePoint 2010.  I'd managed to create the dropdowns but only using fixed code, so if a subsite was created, the code would have to be changed too.

I found a few helpful resources online, but the best for me was http://css-tricks.com/simple-jquery-dropdowns/.  I've included my code below.

SharePoint 2010 master.page

<SharePoint:AspMenu
ID="TopNavigationMenuV4"
EncodeTitle="false"
Runat="server"
EnableViewState="false"
DataSourceID="topSiteMap"
AccessKey="<%$Resources:wss,navigation_accesskey%>"
UseSimpleRendering="true"
UseSeparateCss="false"
Orientation="Horizontal"
StaticDisplayLevels="2"
MaximumDynamicDisplayLevels="2" 
SkipLinkText=""
CssClass="s4-tn"></SharePoint:AspMenu>




jQuery
$(document).ready(function() { 
 //Hide all submenus - this should probably be done in CSS
 $('.menu-horizontal ul ul').hide(); 
 //If a child is selected, I also want the parent to be selected
 $('.menu-horizontal ul ul li.selected').parent().parent().addClass('selected');
 //When the mouse hovers over a parent:
 $('.menu-horizontal ul li').hover(function() {
  //Show the first (and only) child ul, containing a li for each subsite
  $('ul:first',this).show();
 }, function(){
  //Hide the child ul after the mouse has left
  $('ul:first',this).hide();
 });
}); 




CSS

This is all very customised to my SharePoint 2010 environment - the above should work out of the box.

Wednesday, May 16, 2012

Copying files between document libraries with SharePoint Designer Workflow

I have a situation where multiple document libraries exist, and when any document in any library is published and approved, the document should then be copied to a "Public" library. I managed to do this using SharePoint Designer, and creating a custom workflow that activates when an item is changed. The workflow checks to ensure that the document is not checked out, that the version number ends in ".0" (so is a major/published version), and that the document's approval status is "Approved".

However, the problem lies with the fact that when SharePoint workflow is designed to copy the list item to another list/library, but an item with the same name exists, the default action taken is to create a new copy, with the date and time appended to the new item's name. I don't want this. So I've found this article which involves manually editing the workflow's XOML file. It's written for SharePoint Designer 2007 but I didn't have any problems carrying out the same function in SharePoint Designer 2010.

Tuesday, April 24, 2012

Delaying an autocomplete jQuery search

I recently implemented an autocomplete function on a site, using jQuery to update the results and a JsonResult controller to get the actual data.  When the content of a search box changed (using event trigger onkeyup), it would perform a search on the contents of that text box.  I've gone a few steps further and split the text box on spaces and commas, to create a wildcard search, but that's not the point of this post.

The issue I saw is that a new call to the database or cache was being carried out with every keypress.  This was a problem.  So I found some code on http://jsbin.com/ebela4/8/edit#javascript,html and adapted it slightly.  Here it is:


var delay = 600;
var isLoading = false;
var isDirty = false;

function AutoCompleteChange() {
if (!isLoading) {
// This only performs a search if four or more characters have been
// entered. Change the number to change this restriction.
if ($("#txtSearch").val().length >= 3) {
isLoading = true;
setTimeout(function () {
isLoading = false;
if (isDirty) {
isDirty = false;
// Perform actual search
// INSERT YOUR CODE HERE
}
}, delay);
};
};  
};

Thursday, April 19, 2012

SharePoint 2013 - potential screenshots

These are either actual screenshots of SharePoint 2013, or are someone's personal mockups of what SharePoint might look like under the Metro layout.  Either way, looks pretty.

http://www.alexmanchester.com/alexmanchester/2012/04/is-this-how-sharepoint-2013-will-look-sharepoint-metro.html

SharePoint HTML5 master page

Not much use to me in the current situation, as many users are still using browsers that don't support HTML5, but good to know for the future - a HTML5 master page for SharePoint 2010 that looks very shiny indeed.

http://kyleschaeffer.com/sharepoint/v5-responsive-html5-master-page/

Wednesday, April 18, 2012

SharePoint & SAP - getting started

I am beginning work on a contracts management site in SharePoint in the next few months.  Part of the requirements of this site is for contracts to be assigned to particular customers (amongst other things).  It would be very easy for me to implement a simple SharePoint list with a list of customers, but this would be boring, dull and intuitive, as the user would have to add a customer each time.  It would be easy just to grab the customer records from SAP, which we use for SAP-type things.

The team that administrate SAP have been using SAP Business Connector for some years, and (as is the norm, from what I understand) are reluctant to stop using this.  Part of the reason for this is that this team believe they would lose some control over SAP.  However, we have a steadily-growing BizTalk implementation, and to get SAP piped into BizTalk would be a massive selling point not only for future users of SharePoint, but for other applications also.

Here is some quick research that I did on the subject. I imagine there will be more.

Working with SAP Business Connector would seem to require the purchase of additional software such as ERPConnect Services by Theobald Software - http://www.theobald-software.com/en/products/erpconnectservices.htm .  Alternatively, if SAP Business Connector can expose XML, a custom development could be written in .NET, using SharePoint Business Connectivity Services (BCS), but this would require development time.

Working with BizTalk is extremely simple and well-documented - for example, there is a whole series of blog posts on this exact topic here: http://kentweare.blogspot.de/2009/10/sharetalk-integration-sharepointbiztalk.html which was the first site I found when carrying out a search on this.

Essentially, it is possible to communicate with any service or server to any other service or server - everything is possible.  Things just require time and money.  Using BizTalk introduces nothing but benefits, so it's just a case of convincing everyone to accept change.

Expect more posts on this subject as the project continues.  Even with the massive userbase of BizTalk and SharePoint, there does seem to be a bit of a gap when it comes to SAP (with the exception of the above link) and I'm sure I'll be finding things out.

Thursday, April 5, 2012

Parsing XML with : in the name

When parsing XML in C#, problems arise when XML contains a colon (:).  For example, the start of last.fm's resulting XML when performing a search looks like this:


<results xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/" for="tsin-tsi">
<opensearch:Query role="request" searchTerms="tsin-tsi" startPage="1"/>
<opensearch:totalResults>43</opensearch:totalResults>
<opensearch:startIndex>0</opensearch:startIndex>
<opensearch:itemsPerPage>30</opensearch:itemsPerPage>

It turns out that these "opensearch" elements are actually part of the opensearch namespace.  It might not seem relevant for a text-based result file like XML, but it is - you have to declare that namespace before you can grab the totalResults, startIndex or itemsPerPage.  To get the number of results, I used this code:

XNamespace opensearch = "http://a9.com/-/spec/opensearch/1.1/";
XElement pagecount = doc.Descendants(opensearch + "totalResults").FirstOrDefault();
numberofResults = Convert.ToInt32(pagecount.Value);


Wednesday, April 4, 2012

Passing Display Language from SharePoint 2010 to a Silverlight app

This is related to cultures, my current obsession.  Running a Silverlight app in a browser will allow you to change the browser language and have the Silverlight app respond appropriately.

SharePoint 2010 has a "Display Language" option for each user - as long as the appropriate SharePoint 2010 language pack has been installed, the SharePoint site will be changed to show translations that have been assigned for that language.  However, as the browser language hasn't changed, Silverlight web parts don't reflect that change.

The solution?  Create a new Silverlight web part control that inherits from System.Web.UI.WebControls.WebParts.WebPart, and in the Render section (that outputs to a supplied HtmlTextWriter called writer) -

            string s = String.Format(@"              
                <div id='SilverlightObjectDiv_{0}' style='display: block; overflow:hidden'>
                    <object class='ms-dlgDisable' id='SilverlightObjectTag_{0}' width='100%' height='{2}'
                        data='data:application/x-silverlight-2,'
                        type='application/x-silverlight-2' >
                        <param name='source' value='{1}'/>
                        <param name='initParams' value='MS.SP.url={3},culture={4}'/>
                        <param name='culture' value='{4}' />
                        <param name='uiculture' value='{4}' />
                        <!-- Display installation image. -->
                        <a href='http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0'
                            style='text-decoration: none;'>
                            <img src='http://go.microsoft.com/fwlink/?LinkId=108181'
                                alt='Get Microsoft Silverlight'
                                style='border-style: none'/>
                        </a>
                    </object>
               <iframe id='_sl_historyFrame' style='border-bottom: 0px; border-left: 0px; width: 0px; display: block; height: 0px; visibility: hidden; border-top: 0px; border-right: 0px'></iframe>&#160;
                </div>      
                ",
                 this.ClientID.ToString(),
                 _xapFileUrl,
                 (this.Height.Value - 20).ToString(),
                 SPContext.Current.Web.Url, System.Threading.Thread.CurrentThread.CurrentUICulture.ToString()                
                 );


            writer.Write(s);

The important thing to note here is the "culture" and "uiculture" params - System.Threading.Thread.CurrentThread.CurrentUICulture would normally pass the browser language but when running as a SharePoint web part, seems to pass the chosen display language instead.

Tuesday, April 3, 2012

Silverlight Localization

Two useful links for localizing Silverlight applications:

http://www.michaelsnow.com/2010/04/28/silverlight-tip-of-the-day-7-localized-resources/ walks you through the process of making your app culture-observant.

http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2008/10/27/10864.aspx enables you to use your browser language settings to determine the culture automatically.

Wednesday, March 28, 2012

FLV files in SharePoint

Microsoft Expression Encoder. 'Nuff said. I keep finding out new reasons to love Microsoft Expression. It's still missing a good photo editing option though.

Converters in XAML

On a Silverlight course this week. When working with data in WPF or SilverLight, you can bind data to properties, such as background colour. However, you will need to convert it to an actual colour. This can be done in WPF using triggers, but if you want to use code, create a new class that inherits from IValueConverter, and you can use that helpful little "Apply Data Binding" wizard to assign the converter to a property. Very useful.

Thursday, March 22, 2012

Custom CSS and Master Pages in SharePoint 2010

The SharePoint site I administrate uses massively customised CSS whilst still retaining the feel of SharePoint navigation.  It implements expanding (jQuery) navigation on the left hand side, which is fiddly but works for now.  This post could become useful when I need to develop and test without worrying about affecting existing users on a live site.

https://www.nothingbutsharepoint.com/sites/eusp/Pages/Create-Custom-CSS-and-Master-Page-in-SharePoint-2010.aspx

Page breaks in Word

Having not used Microsoft Word an awful lot, I simply resorted to inserting page breaks to keep content together. I realised this isn't a very clever thing to do, so I looked for a better way to do things. I found this interesting article about managing pagination in Word. Hurray!

Managing pagination (or, avoiding page breaks where you don't want them) | ShaunaKelly.com