Harnessing Client-Side Technologies to Enhance your SharePoint Site

Yesterday I had the privilege of presenting at the Perth SharePoint User Group. The experience was sensational; the session went off without a hitch, the crowd was large, attentive and full of questions. Feedback both immediately after the session and that which filtered through later was very positive. As promised and to wrap up the event I thought it was only fitting that I posted my slides and a summary of the presentation – this is that post.

The day started off with a little milestone – something I’ve yet to see myself previously and which I expect is relatively rare for the PSPUG

100 tickets – the last one snapped up only hours before the event. I’ve been told Microsoft (who I must thank for allowing us to use their excellent facilities every month) was contacted on the day to hunt down some more chairs for the room. At best guess I’d say there was about 70-80 people who made it – evident by the few chairs left empty and the speed at which 15 family sized pizzas were demolished.

But enough with the background, onto the presentation itself. I had set it up to be roughly 30 minutes of slides and discussion and 30 minutes of demo. In the end I cut the demo’s a bit short – I figured it was more important to answer a few questions than explain the detail of what was going on for each one. I’ll fill the blanks in this post pointing off to the resources you’ll need to replicate the demos yourself. Along with posting the slides I’ll also jot down some of the main points I was trying to get across in each one.

Who am I

You may think this slide needs no explanation but I want to stress the point I made in the session again. While i’ve only recently joined twitter, the little gems of information I’ve been able to garner in that time have been extremely valuable. I avoided twitter like the plague, mostly due to an impression it was predominantly noise. It is. But if you can get past that you can get to its true value and keep in touch with what’s going on in the wider SharePoint community.

Agenda

I was tossing up between deep diving into one of the technologies or doing a broad brush overview of the lot – clearly the latter approach was taken. The goal of the session was to inform and inspire – there is plenty of information out there to read up on at a later date – some of which i’ll outline in this post.

JavaScript

Not much to say here – bit of an obligatory slide to round out the presentation as a whole. We’ll move on.

jQuery & The basics

Aside from the information, the main point I want to highlight is that jQuery really isn’t that scary. It simplifies JavaScript a great deal and the learning curve really isn’t that steep compared to the benefits you’ll gain from using it. It’s quite performant and efficient when compared to some of the .NET based AJAX libraries out there, it’s become one of my favourite technologies to use when creating public facing websites and I hope I impressed at least a few in the audience enough to give it a shot. For a primer on some of the basics and functions jQuery provides, have a read of the documentation.

Tips and Tricks

There was a fair few tips and tricks to cover in the session – rather than explain each one i’ll attempt to link off to a resource which will do the job for me.

Putting jQuery into noConflict mode

Making use of jQuery(document).ready()

Leverage callbacks

Don’t write scripts directly into the Content Editor Web Part

(Keep in mind it may not be suitable for staged environments with content deployment – converts the link to an absolute url!)

Know your options for referencing jQuery

(Although I don’t have a problem with referencing on the master page)

Enable Intellisense in Visual Studio

Know your options for debugging

(Don’t forget about the console window in Firefox! Can be very useful. Also, remember that different browsers can often end up shedding the light on your issue, so don’t discount debugging in multiple browsers)

It would be remiss of me not to give a shout out to Chris O’Brien considering the last 2 links are from his blog. In fact, all the posts of that series are worth a read so I’d definitely encourage you to do just that.

Plugins!

The true power of jQuery lies in the multitude of plugins available to be used. They can be a huge time-saver and with a bit of knowledge you can often customise them to suit your needs. There are hundreds of quality resources out there explaining which plugins are great – i’ll leave it up to you to explore!

Knockout

Knockout is fantastic. I can’t speak of it highly enough – do yourself a favour and check out the documentation and live examples to get a taste of what it is capable of. I could have done a whole presentation on Knockout itself – John Liu does a great one in his session SSPUG retrospective: Creating Knockout user experiences in SharePoint with JavaScript for those Aussies amongst us, definitely check it out if you get a chance. I hope I did it justice to peak your interest enough to dive deeper into the technology. Keep in mind that Knockout works brilliantly with both the jQuery templating libraries (jquery-tmpl or jsRender) and jQuery itself. I mentioned that while I’ve used jquery-tmpl in the past, it has been discontinued so you may want to look into jsRender instead.

SignalR

This is another topic that could have had a session to itself. In fact, Christian Heindel already did – it was nice to see myself in someone elses slides half a world away! I didn’t dive too heavily into this, it was more of a primer to spark some interest and simply let the demo do the talking.

Retrieving Data on the Client Side

To date I had only discussed the tools available to present information on the client – this slide was all about how we got that information in the first place. Rather than link to resources here (it should be really easy to search for them yourself) what I’ll do is outline my main points discussed.

Client Object Model: handy tool provided in SP2010. Need to learn the syntax which is different from the standard OM and should definitely know some CAML. Important to consider performance when writing this code.

REST services: absolutely love them. Extremely valuable with their ability to return JSON data simply by structuring the URL with query string. Their ability to return JSON makes them great to combine with Knockout or jQuery.

SPServices: extremely valuable library written by a member of the SharePoint community Mark D Anderson. Perfect for those stuck on SP2007 but also valid for SP2010. Have a read of the debate going on regarding when you should use one or the other.

HTTP Handlers: another tool at your disposal in SP2007. To be honest the main reason I have used these is because I wasn’t truely aware of the power of SPServices. The general premise is you fire off to the HTTP Handler which runs some server side code, serializes the data into JSON and returns it back via Response. Still handy if you simply MUST have JSON in SP2007.

Content Query Web Part: not strictly a client side technology, but it is so powerful out of the box that it deserved a mention. Combined with a bit of XSL it is truely one of if not the best web parts provided to us in SharePoint.

Demos!

And then it was time for the fun stuff. I considered recording all of the demo’s to place here but it wouldn’t have been as relevant without the accompanying dialogue, and I didn’t really want to record my own voice. I’ve settled for links to the inspiration behind each demo – if you have any questions feel free to ask and I will go into more detail.

Demo 1: Client Object Model & jQuery

This one came from a tweet I noticed a week before my presentation. Take a look at Using the SharePoint Client Model to populate a jQuery AutoComplete box by Douglas Leung. The delay you get before the autocomplete kicks in is because we’re waiting for SharePoint to load the relevant scripts before we bind the event.

Demo 2: SPServices & jQuery

Thankfully I dropped the right name in my presentation! It was Mark Rackley responsible for the inspiration behind An Easy to Use Content Slider. Sure his version isn’t as good as the one which featured some of the best the mighty Fremantle Dockers have to offer, but the general vibe of the slider is the same 😉

Demo 3: Content Query Web Part + XSL + jQuery

I’ve said all I need to say about this one in my post Using the Content Query Web Part and jQuery to create a staff desk locator so if you’re interested pop over to that post and take a look at the nuts and bolts holding it together. One point I did mention in the presentation is that the solution kind of grinds to a halt in the older versions of IE due to the struggles it has with larger-scale manipulation of the DOM.

Demo 4: REST services + Knockout + TMPL + jQuery slider + jQuery validation + jqPlot

Yes, there was a lot to this one. I did create this demo specifically for the session but my post on Applying the MVVM pattern to create SharePoint list-driven interactive tools using Knockout covers the jist of it. If not, just drop me a line.

Demo 5: SignalR

Definitely a topic which gets me pretty excited. The demo I showed was the same one I posted in Harnessing SignalR in SharePoint – complete with video! So check it out if you want a look under the hood at how we made that happen.

And that pretty much covers it. There were some great questions to follow the session, enough so that all of the left over SP Saturday ‘limited edition’ USB key’s were given away. I truely appreciated the opportunity to present again at the user group and was humbled by the turnout. I hope a few people were inspired to go out and try some of the technologies presented in their own SharePoint environments.

I’ll sign off with a couple of tweets I received after the session – one in particular that made the whole session worth while!

Search Engine Optimisation (SEO) for SharePoint Sites – Part 2

In Search Engine Optimisation (SEO) for SharePoint Sites – Part 1 of this series I looked at the implications content, naming, metadata and structure of your SharePoint site can have on search rankings. This part of the series will focus more on how content can be manipulated to improve search rankings and other factors that can be leveraged from within SharePoint. Finally, Search Engine Optimisation (SEO) for SharePoint Sites – Part 3 will take a step outside the SharePoint box into how you can boost rankings and traffic to your SharePoint site using other methods.

Optimise the Load Time of your Pages

This one works on a couple of levels. Firstly, there is some benefit in terms of SEO for faster loading pages. Take a read of Geoff Kenyon’s article Site Speed – Are You Fast? Does it Matter for SEO?. The reality however is that it isn’t a huge factor. More importantly though is that the time it takes to load your pages has a huge impact on bounce rates and visitors wanting to return to your site, and for that reason it is a critical task to undertake. SharePoint, especially MOSS 2007, doesn’t have the fastest load times therefore anything you can do to reduce the load time of your pages is paramount. I intend on writing a post on this in the future so i’ll leave it at that for now, but it’s definitely something to keep in mind when creating your site.

Maximise your Content to Markup Ratio

There’s a number of articles available discussing the importance (or lack thereof) of maximising your content to markup ratio. Whether you believe it’s a factor or not is largely irrelevant – there are a number of benefits to gain from minimising the amount of markup on a page for both performance and structural reasons. It’s also important to ensure the text content appears as high on the page as possible and this can be affected by manipulating the markup structure of the page. SharePoint is notoriously bad in this regard. Improvements were made from MOSS 2007 to SharePoint 2010 but it’s still not perfectly clean HTML. There are things you can do to improve the situation – using controls instead of webparts in MOSS will minimise the number of tables used on the page. Control Adapters can be used to manipulate the HTML rendered by controls. You can ensure your Master Pages and Page Layouts structure content higher on the page and adjust their location using custom CSS. Often a lot of effort is required when focussing on this SEO technique so unless it’s of critical importance sometimes it’s best to do whatever you can and just live with the rest.

Optimise your Images and Anchor tags

Images and anchor tags are 2 elements within the page content that can be slightly adjusted to provide an extra SEO boost. Images provide the benefit of also being indexed in the Google Images search engine which can provide extra traffic. ALT text is essential when optimising an image. It should be short, sharp, relevant and if possible keyword-rich. File names can also provide some extra juice and should be similar to the ALT text and hyphen-delimited. The Title attribute is unlikely to hold much weight but it can’t hurt and should match the ALT text. Anchor tags are another which can make use of the Title attribute. The other important aspect of anchor tags, particularly intra-site linking which you have control over, is to ensure the text which is hyperlinked is descriptive and keyword rich – preferably matching the key phrase that the page is optimising. In terms of SharePoint there are 2 features you can utilise to reap the rewards – the ALT text field for images and the Tooltip field for hyperlinks. If you want to utilise the Title of an image you’ll need to delve into the code-behind which may not be worth the effort.

Use 301 redirects over 302

As I mentioned in my post 301 vs 302 Redirects in SharePoint there are a number of reasons why 301 redirects are preferred to 302 from an SEO perspective. Rather than going over everything again I’d encourgage you to read through that post and the associated links to understand the benefits and potential solutions around it. It’s significant in a SharePoint context because when accessing a site by its URL directly rather than the specific page URL, a 302 redirect is used to take you to the Welcome Page. The Redirect Page layout in SharePoint also uses 302 redirects.

Don’t Destroy Old Content

No matter how old a page, article or news item is, it still has the ability to appear in the search results and drive people to your site. In fact, the age of a site often has a positive affect on its ranking. Why would anyone want to simply discard all the effort that went into optimising the page in the first place because it was deemed somewhat out of date? Archives are a great and suitable alternative which maintain the content online and hence the rankings of the page. SharePoint makes this quite easy to do – simply filter a Content Query Web Part to return all out of date pages on an ‘Archive’ page and you’re set. The process can either be automated by scheduling an end date for the page, or by manually configuring a custom column of the page. You could also move it into an ‘Archived’ sub-site, however if this option is chosen, keep in mind the redirect that will be required and the fact that will be a 302 redirect by default.

Avoid Flash and Silverlight if you can

It’s common knowledge that using Flash or Silverlight on a website in place of indexable content is a search ranking destroyer. Flash has made SEO improvements to the format but this is still only limited. With HTML5, CSS3 and jQuery taking off and able to achieve a lot of the rich interactive functionality provided by Flash and Silverlight, you’d have to question the use of it on your search engine optimised site. This goes for SharePoint as much as any other platform – leveraging the jQuery library in SharePoint in particular is easy to do and leaves the site more SEO friendly than a Flash or Silverlight equivalent.

Create an XML Sitemap Automatically

I’m not sold on the benefits of XML Sitemaps to be perfectly honest. I tend to agree with an article written by Matt McGee titled XML Sitemaps: The Most Overrated SEO Tactic Ever in that all an XML Sitemap is really doing is masking and potentially even causing problems. There are many more however that argue the opposite, such as Bruce Clay’s XML Sitemaps in SEO – Part 1. If you’re going to go down the XML Sitemap path, and i’m not going to categorically say it’s something you shouldn’t do, I’d suggest that you ensure that it is constructed automatically so it is completely up to date. In SharePoint one way this can be achieved is via Waldek’s Imtech XML Sitemap or Mavention XML Sitemap for 2010.

Include a Robots.txt File

Having a Robots.txt file for your site is not so much about improving the rankings of pages in your site but more about ensuring pages you don’t want appearing in search results aren’t indexed. For more information about Robots.txt have a read of Robots.txt: All you need to know. This is particularly important for SharePoint because often there are a number of pages you simply wouldn’t want indexed – list views and the like – which can sometimes end up being crawled. I’d definitely suggest including this file for your SharePoint site as part of your overall SEO strategy.

Structure your Content with Heading Tags

Using header tags (H1 through to H6) to structure content is another tool at your disposal to infer importance on particular terms and phrases. Header tags are easily applied in SharePoint via the content editor so its important to stress 2 main concepts. Firstly – use them only for the keywords or phrases. Too often heading tags are used unnecessarily throughout the page or on sub-headings which really convey no SEO benefit to the page. Secondly – use them for the keywords and phrases rather than styling DIVs or SPANs to achieve the same visual effect – it’s the tag which is recognised, not the size of the text on the page. You can have the desired visual effect on the page by using heading tags where appropriate and styling other text with CSS if the term or phrase is not a targeted keyword for the page.

Externalise your JavaScript

This one ties in to maximising your content to markup ratio – the less text on the page with no SEO benefit the better. There are also performance trade-offs however – you don’t want to be creating a bunch of extra page requests to pull down each individual JS file that contains your page’s code. There are also development implications – sometimes it’s easier to code the relevant JavaScript directly within a control rather than having to find it and work on it seperately. Ultimately it comes down to what is more important to you. In a SharePoint context i’d recommend using one file to hold all JavaScript functions that will need to be called from various pages, reference it via the SharePoint:ScriptLink control and make the call to the function from within your control. This minimises the amount of JavaScript text on the page and minimises the page requests.

Add ‘Strength’ to Keywords

This one I think would be extremely minor if it has any influence at all – but I guess you never want to miss an opportunity so it’s something worth discussing. Take a read of Traian Neacsu’s article Bold or Strong Tag and SEO – Complete HTML Reference Guide for SEO for more information on the topic. The main take away is that styling via CSS won’t have any affect, while bolding keywords with the STRONG element may be recognised. It’s something worth considering anyway.

In Search Engine Optimisation (SEO) for SharePoint Sites – Part 3 of this series I’ll take a step outside the SharePoint box into how you can boost the rankings and traffic to your SharePoint site using other methods.

Using the Content Query Web Part and jQuery to create a staff desk locator

I recently took it upon myself to provide an alternative to the staff desk locator that was due to go to production in the near future. The back-story is that a requirement existed to locate staff on a floor map to assist with the move to a new office complex. The original concept was created by another developer, but with multiple requirement changes and multiple developers tweaking the original solution, the end result was far from perfect. It only worked completely in IE7 standards browser mode and some of the functionality performed in a less than ideal fashion – think scroll bars to navigate around a zoomed in map amongst other imperfections.

Now the solution I present here is also far from perfect, I have no illusions about that. I debated whether to post about it at all until I had a chance to optimse and refine it seeing that what exists here is essentially a protoype, however I believe it is in a complete enough state to provide some benefit or interest to people out there. If anyone can propose some better ways of achieving some of the functionality demonstrated here I’d be more than interested in hearing it. The first half of the post will explain the journey, so if you’re not interested in what drove the implementation decisions along the way, feel free to skip down to the solution.

The Journey

The concept I was going for was something similar to Google Maps; your standard zoom, move, mouse-wheel and drag functionality. The most attractive option I could find was a paid solution called MapBox which can be seen in their various featured map demos. I didn’t think a paid solution would be an option and besides, I wanted to see if I could create something similar myself. I found a jQuery plugin also called MapBox documented at Mapbox: Zoomable jQuery Map Plugin which definitely appeared to get me half way there. The only remaining challenge was to plot the points on the map and get it driven via SharePoint.

Plotting the points definitely proved challenging. I went through a few iterations as I lead myself down some wrong paths – the joys of experimentation. I went so far as to get everything adjusting correctly from a movement point of view (thinking I was better off starting easy and leaving the complicated functionality for later) only to have it breaking during zoom. It wasn’t until I further investigated the zoom requirements and changed the way in which I was approaching the challenge that I realised the work I had done to adjust for movement was redundant and was handled by the plugin. Lovely.

I eventually got the locations remaining in place during zooming however this exposed its own issues; the pins representing each location didn’t adjust in size as the map did, resulting in a very strange looking experience. It is here where my solution is least elegant – I essentially created multiple CSS classes and multiple sized images and dynamically changed the class of the elements as I zoomed in and out (using the width of the parent div to differentiate). I couldn’t simply scale the image as I was using background images and divs rather than image elements (for reasons based on issues with the previous solution and brief experimentation in this one). This exposed even more gotchyas – it appears different browsers round decimal values in different ways (there were variations around rounding, floor or ceiling). I also noticed that while on most occassions the plugin would zoom to fixed width sizes, occassionally it would result in completely unexpected values which I needed a safety net CSS definition for. I love Internet Explorer. The CSS is hence a pretty ugly beast, but for the most part it works.

The next step was adding some hover over functionality to display the staff members information. This was relatively simple and just leveraged the onMouseOver event and some jQuery for each plotted element. The search functionality was a little more complex. I had witnessed the functionality I desired during my research, present on the Rock ‘n’ Roll Metro Map, which I dug deeper into and discovered it was being achieved via the animate function of the jQuery UI plugin. A couple of equations and experimentation later to determine how I could establish my scroll-to points and it was all humming along nicely.

The final challenge was getting it all into SharePoint. This functionality was to sit on the intranet which had been created as a codeless solution, therefore rather than rendering what I needed via the API in a web part I decided to stick with the theme and leverage the Content Query Web Part (CQWP), drawing out of a custom list. The previous solution had done the same so it wasn’t too difficult with a few tweaks to achieve what I desired. A couple of the gotchyas encountered included ensuring the CommonViewFields were set in the CQWP (exporting and importing the web part was used to achieve this) and rediscovering that for a column to exist to filter on in the web part it needed to be a site column, not just a list column. One last little hurdle that needed to be jumped included how the parts sat on the page with the master page being used – and this differed between browsers (Did I mention how much I love IE?). Seeing this probably had a lot to do with the custom master page being used I’ll leave the specifics out in the solution below, however just know that it is something that may need to be considered.

The Solution

Part 1: The list to hold the data

This was a custom list with some custom columns. Note that the IsAResource column is a site column. The -1000 X and Y values reflect a staff member that does not exist in the building.

Part 2: Header.xsl

The templates in Header.xsl map to the Group Style settings selected in the Content Query Web Parts. They render once to open and close the items which are rendered in between. The below templates were defined.

  <xsl:template name="DeskfinderSelectName" match="*[@GroupStyle='DeskfinderSelectName']" mode="header">
    <script type="text/javascript">document.write('<![CDATA[<div id="SearchByName"><span class="SearchLabel">Search by Name:</span><select onchange="FindUser(this.value);"><option value="">Please select an option...</option>]]>')</script>
  </xsl:template>
  <xsl:template name="DeskfinderSelectNameClose" match="*[@GroupStyle='DeskfinderSelectName']" mode="footer">
    <script type="text/javascript">document.write('<![CDATA[</select></div>]]>')</script>
  </xsl:template>
  <xsl:template name="DeskfinderSelectResource" match="*[@GroupStyle='DeskfinderSelectResource']" mode="header">
    <script type="text/javascript">document.write('<![CDATA[<div id="SearchByResource"><span class="SearchLabel">Search by Resource:</span><select onchange="FindUser(this.value);"><option value="">Please select an option...</option>]]>')</script>
  </xsl:template>
  <xsl:template name="DeskfinderSelectResourceClose" match="*[@GroupStyle='DeskfinderSelectResource']" mode="footer">
    <script type="text/javascript">document.write('<![CDATA[</select></div>]]>')</script>
  </xsl:template>
  <xsl:template name="DeskfinderPin" match="*[@GroupStyle='DeskfinderPin']" mode="header">
    <script type="text/javascript">document.write('<![CDATA[<div class="StaffLocations">]]>')</script>
  </xsl:template>

The most important part to note here is that the DeskfinderPin doesn’t have an associated footer. This was a bit of a gotchya – the browsers closed off the div anyway but if I included the footer, the

<script type="text/javascript">document.write('<![CDATA[</div>]]>')</script>

node was appearing within the div which caused issues when I went to extract the HTML via jQuery and pump it into other locations. It worked fine without the footer so it was removed.

Part 3: ItemStyle.xsl

The templates in ItemStyle.xsl map to the Item Style settings selected in the Content Query Web Parts. They’re rendered for each item in the query. The below templates were defined.

  <xsl:template name="DeskfinderSelectOption" match="Row[@Style='DeskfinderSelectOption']" mode="itemstyle">
    <xsl:variable name="DisplayTitle">
      <xsl:call-template name="OuterTemplate.GetTitle">
        <xsl:with-param name="Title" select="@Title"/>
        <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
      </xsl:call-template>
    </xsl:variable>
    <option value="{@ID}">
      <xsl:value-of select="$DisplayTitle"/>
    </option>
  </xsl:template>
  <xsl:template name="DeskfinderPin" match="Row[@Style='DeskfinderPin']" mode="itemstyle">
    <xsl:variable name="DisplayTitle">
      <xsl:call-template name="OuterTemplate.GetTitle">
        <xsl:with-param name="Title" select="@Title"/>
        <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="starts-with(@Location,'L2')">
		<div class="pin2" style="left:{@X}px; top:{@Y}px;" onMouseOver="DisplayDetails(this);" data-id="{@ID}" data-xpos="{@X}" data-ypos="{@Y}" data-name="{$DisplayTitle}" data-phone="{@Phone}" data-email="{@Email}"></div>
      </xsl:when>
      <xsl:otherwise>
        <div class="pin3" style="left:{@X}px; top:{@Y}px;" onMouseOver="DisplayDetails(this);" data-id="{@ID}" data-xpos="{@X}" data-ypos="{@Y}" data-name="{$DisplayTitle}" data-phone="{@Phone}" data-email="{@Email}"></div>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

It is important to note that all of the custom columns referenced in the template (X, Y, Email, Phone) need to appear in the CommonViewFields element of the Content Query Web Part to be recognised. This can be done by exporting the web part, manually editing the .webpart file to add those columns and importing it back onto the page.

Part 4: The resources

CSS – the CSS used was extended from that used for the MapBox demo with some slight tweaks to those definitions. A little more was added to style the drop down menus and staff information. The majority of CSS was to style the pins mapping to the locations of staff members. As mentioned before, this was a very bloated solution and far too much to include in this post. I’ll try and summarise.

There were essentially 4 groups of styles – level 2 pins, level 3 pins, level 2 ‘located’ pins and level 3 ‘located’ pins. These were seperated into whether they appeared in the firstLayer, secondLayer, thirdLayer or fourthLayer. They were further seperated into sizes mapping to the width of the parent div (the reason for 2 sizes 1 pixel apart is due to the rounding issue I mentioned earlier). Here are a few randomly selected examples:

#firstLayer .pin2
{
	position: absolute;
	width: 10px;
	height: 9px;
	background: url(../PublishingImages/pin2-size709.gif);
}

#secondLayer .pin2.found.size974, #secondLayer .pin2.found.size975
{
	width: 16px;
	height: 16px;
	background: url(../PublishingImages/found2-size975.gif);
}

#fourthLayer .pin3 {
	position: absolute;
	width: 30px;
	height: 27px;
	background: url(../PublishingImages/pin3-size2126.gif);
}

#fourthLayer .pin3.size1505, #fourthLayer .pin3.size1506 {
	width: 21px;
	height: 19px;
	background: url(../PublishingImages/pin3-size1506.gif);
}

You get the idea. Perhaps look into an alternative solution!

Images – The images included the 4 different sized floor map images, the map control image and 100 different pin images to match the CSS styles.

JavaScript – The javascript leveraged that provided with the MapBox demo with the following adjustments.

Functionality to populate the staff information:

function DisplayDetails(person)
{
	$("#staffName").text($(person).attr("data-name"));
	$("#staffPhone").text($(person).attr("data-phone"));
	$("#staffEmail").html('<a href="mailto:' + $(person).attr("data-email") + '">' + $(person).attr("data-email") + '</a>');
}

Functionality to find a given user or resource:

function FindUser(id)
{
	var limitX = 0, limitY = 0, mapWidth = $(".mapwrapper").width(), mapHeight = $(".mapwrapper").height(),
	nodeWidth = $(".current-map-layer").width(), nodeHeight = $(".current-map-layer").height();

	if(mapWidth < nodeWidth) limitX = mapWidth - nodeWidth;
	if(mapHeight < nodeHeight) limitY = mapHeight - nodeHeight;

	var element = $(".current-map-layer div[data-id='" + id + "']");
	var left = (parseInt($(element).css("left")) * -1) + (parseInt($(".mapwrapper").css("width")) / 2);
	var top = (parseInt($(element).css("top")) * -1) + (parseInt($(".mapwrapper").css("height")) / 2);

	left = (left > 0) ? 0 : left;
	left = (left < limitX) ? limitX : left;
	top = (top > 0) ? 0 : top;
	top = (top < limitY) ? limitY : top;

	var width = parseInt($(".current-map-layer").css("width"));
	if ($("div[data-id='" + id + "']").attr("class").indexOf("pin2") == -1)
		$("div[data-id='" + id + "']").attr("class", "pin3 found size" + width);
	else
		$("div[data-id='" + id + "']").attr("class", "pin2 found size" + width);

	$(".current-map-layer").animate({top: top,left: left}, {easing: "easeOutQuart"});
	DisplayDetails(element);
}

The important thing to note here is that extra code was required to ensure the map wasn’t moved ‘out of viewport’. Thankfully, the MapBox JavaScript already included that logic for its drag functionality so I just leveraged that.

Document Ready functionality:

$(document).ready(function() {
	$("#firstLayer,.mapcontent").html($(".StaffLocations").html());

	$("#viewport").mapbox({
		mousewheel: true,
		layerSplit: 8//smoother transition for mousewheel
	});
	$(".map-control a").click(function() {//control panel
		var viewport = $("#viewport");
		//this.className is same as method to be called
		if(this.className == "zoom" || this.className == "back") {
			viewport.mapbox(this.className, 2);//step twice
		}
		else {
			viewport.mapbox(this.className);
		}
		return false;
	});
})

The first line is the most important – this is where the HTML is taken from the hidden div rendered by our Content Query Web Part and injected into the necessary locations. The rest of the code is the same that existed for the MapBox demo.

Custom changes to the _zoom function (insert above return movement; line)

/* Custom changes */
var xChange = (totalWidth == undefined) ? 1 : totalWidth / $("#firstLayer").width();
var yChange = (totalHeight == undefined) ? 1 : totalHeight / $("#firstLayer").height();

$(".pin2,.pin3").each(function(index, value) {
	$(this).css("left", ($(this).attr("data-xpos") * xChange));
	$(this).css("top", ($(this).attr("data-ypos") * yChange));

	var width = parseInt($(".current-map-layer").css("width"));
	if ($(this).attr("class").indexOf("pin2") == -1)
	{
		if ($(this).attr("class").indexOf("found") == -1)
			$(this).attr("class", "pin3 size" + width);
		else
			$(this).attr("class", "pin3 found size" + width);
	}
	else
	{
		if ($(this).attr("class").indexOf("found") == -1)
			$(this).attr("class", "pin2 size" + width);
		else
			$(this).attr("class", "pin2 found size" + width);
	}
});
/* End Custom changes */

Part 5: Referencing the resources

This was a chromeless Content Editor Web Part referencing the required resources:

<link href="../Documents/master.css" type="text/css" rel="stylesheet">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>
<script type="text/javascript" src="../Documents/mousewheel.js"></script>
<script type="text/javascript" src="../Documents/mapbox.js"></script>

Take note the version of jQuery referenced – it’s not the latest, but was required for MapBox to function correctly.

Part 6: The search box

This was a chromeless Content Query Web Part pointing to the list in Part 1. It was set to show items of type ‘Custom List’ and filtered where ‘IsAResource is equal to 0’. The Presentation was grouped by <Site> (to ensure the group element was rendered) and sorted by ‘Title ascending’. The ‘Limit the number of items to display’ was turned off. The Group Style was set to ‘DeskfinderSelectName’ and Item Style set to ‘DeskfinderSelectOption’.

To include a Search by Resource box as well as a Search by Name box, the steps above can be followed, however the filter value should be 1 and the Group Style should be set to ‘DeskfinderSelectResource’.

Part 7: The map, associated controls and staff information

This was a chromeless Content Editor Web Part which essentially rendered the HTML which existed in the MapBox demo with a few minor tweaks.

<div class="mapwrapper">
	<div id="viewport" style="overflow: hidden; position: relative;">
		<div id="firstLayer" class="current-map-layer" style="background: url('Images/FloorImage1.png') no-repeat scroll 0% 0% transparent; width: 709px; height: 631px; position: absolute; display: block; left: 0px; top: 0px;">
		</div>
		<div id="secondLayer" style="height: 947px; width: 1063px; position: absolute; display: none;">
			<img style="width: 100%; position: absolute; left: 0pt; top: 0pt;" src="Images/FloorImage.png" alt="">
			<div style="position: absolute; left: 0pt; top: 0pt; background: none repeat scroll 0% 0% white; opacity: 0;" class="map-layer-mask"></div>
			<div style="position: absolute; top: 0pt; left: 0pt;" class="mapcontent">
			</div>
		</div>
		<div id="thirdLayer" style="height: 1262px; width: 1417px; position: absolute; display: none;">
			<img style="width: 100%; position: absolute; left: 0pt; top: 0pt;" src="Images/FloorImage.png" alt="">
			<div style="position: absolute; left: 0pt; top: 0pt; background: none repeat scroll 0% 0% white; opacity: 0;" class="map-layer-mask"></div>
			<div style="position: absolute; top: 0pt; left: 0pt;" class="mapcontent">
			</div>
		</div>
		<div id="fourthLayer" style="height: 1893px; width: 2126px; position: absolute; display: none;">
			<img style="width: 100%; position: absolute; left: 0pt; top: 0pt;" src="Images/FloorImage.png" alt="">
			<div style="position: absolute; left: 0pt; top: 0pt; background: none repeat scroll 0% 0% white; opacity: 0;" class="map-layer-mask"></div>
			<div style="position: absolute; top: 0pt; left: 0pt;" class="mapcontent">
			</div>
		</div>
	</div>
	<div class="map-control">
		<a href="#left" class="left">Left</a>
		<a href="#right" class="right">Right</a>
		<a href="#up" class="up">Up</a>
		<a href="#down" class="down">Down</a>
		<a href="#zoom" class="zoom">Zoom</a>
		<a href="#zoom_out" class="back">Back</a>
	</div>
	<div class="staff-information">
		<div><span class="staff-info-label">Name: </span><span id="staffName"></span></div>
		<div><span class="staff-info-label">Phone: </span><span id="staffPhone"></span></div>
		<div><span class="staff-info-label">Email: </span><span id="staffEmail"></span></div>
	</div>
</div>

Note that the images and sizes were adjusted respectively and each layer div was given an id so I could reference them via both jQuery and CSS. The staff-information div was also included to present the staff information.

Part 8: The staff locations

This was a chromeless Content Query Web Part pointing to the list in Part 1. It was set to show items of type ‘Custom List’, grouped by <Site> and the ‘Limit the number of items to display’ was turned off. The Group Style was set to ‘DeskfinderPin’ and Item Style set to ‘DeskfinderPin’.

Note that the contents rendered here were within a div hidden by the CSS – it’s main purpose was to render the data once, then jQuery injected it into the locations required.

The End Result

An image doesn’t really do it justice, but for the purposes of completing the article, the end result looks something like that depicted below.

Either that or you can watch a video below on how the functionality works.

How the Career Centre mobile site was built using SharePoint

I’ve recently had the pleasure of working on a large public facing portal solution for the Career Centre. I came onto the project midway through so inherited a lot of branding and functionality, however early enough to create a number of components from scratch, having an input into the design and implementation decisions along the way. One such piece of functionality was the mobile site which can be accessed on your phones by clicking the link above, or in your browsers by accessing the mobile site directly.

The reason for this post is that it hasn’t been done exactly the way one would imagine. I thought I’d take the chance to explain the decision process behind the implementation and potentially open the lines of discussion on whether the decisions made were valid, based on false logic or whether there was a better solution available. For the purpose of context the Career Centre portal was developed on MOSS 2007 Enterprise Edition.

We’ll start with the requirements. The mobile site design had been provided to us so barring some minor tweaks, was not something that required a large amount of attention. The content of the site was to be a sub-set of the live site; a combination of landing pages which were to differ from the live site, content pages which mirrored the live site exactly, content pages which either differed from or didn’t exist in the live site, ‘multi-part’ content pages (based on a custom page layout) and functional aspects of the site (including but not limited to various search capabilities).

My thoughts were that with a custom master page, mobile-specific page layouts and mobile-specific controls and web parts, the majority of requirements would be easy enough to satisfy, and that largely turned out to be the case. My main concern was with content and ensuring the most efficient solution without resorting to content duplication, and the thought process behind that is what will form the basis of this post.

I had a fair understanding already that the ‘best practice’ way of implementing a mobile site in SharePoint was to leverage the variation capabilities of the product. Some initial research I performed backed up that presumption; the most impressive post I personally found for the topic was Jamie McAllister’s discussion on Exposing your MOSS WCM to Mobile Devices. I’d had some brief exposure to SharePoint variations from my time at TourismWA on westernaustralia.com. There, variations were used for the purposes of multi-lingual sites (the complexity of that implementation is definitely worthy of a post on its own!). I was therefore already hesitant to embrace this functionality for my purpose.

Some extra research I did around variations did little to calm my nerves. Some of the more relevant posts I read include Jamie McAllister’s Do MOSS Variations really SUCK, Alex’s Common Problems with MOSS Variations and Jeremy Jameson’s Dumping MOSS 2007 variations Part 1, 2 and 3. There were a bunch of other negative posts I read that escape me now. For the sake of balance, Stefan Goßner’s series on the Variation Feature should also be read.

My main concern however was that changing a page’s layout from the initial site’s to the mobile-specific layout would cause a variation and hence no longer receive content updates; funnily enough, this proved a non-issue. Through my experimentation however I did come up with a raft of other concerns including;

  1. A variation required the source to be a sub-web of the root – which means we couldn’t have careercentre.dtwd.wa.gov.au, it would have to be careercentre.dtwd.wa.gov.au/careercentre or something to that effect. The varied site would then also be a sub-site like careercentre.dtwd.wa.gov.au/mobile. This may seem like a small issue – but the URL of the site definitely was no small issue for the client.
  1. You can set up variations initially so that they auto-populate the varied site when new sites/pages are created, or you can set them up so they do not. We would have needed to turn that off considering the mobile site was to be a small sub-section of the main site. Problem being, once that feature was turned off we would then have to manually build up any new site structure to match. Once the variation hierarchy has been created automatically it can’t be done again.
  1. When I automatically created this hierarchy, it seemed to pick and choose which pages it recreated. I noticed only 2nd level sub-site pages where being brought across. I also noticed that for each site brought across, instead of recreating the default page it created a new default welcome page. This was obviously a huge concern, and would amount to a fair degree of manual labour to get it all up and running initially.
  1. Lists don’t get brought across and can’t be considered linked-up varied content. That means all the lists (that we needed for the mobile site) would need to be created in the mobile site manually and a process set up to copy content across in the event it was added or changed. This was more programmatic work that we were trying to avoid due to time constraints. (If this is a concern of yours have a read of another of Jamie’s posts)

For these reasons along with the research I had conducted and my own prior bias, rightly or wrongly I decided to turf the idea of using variations and proceeded to investigate an alternate solution.

Thanks to a huge degree of good fortune I had only days before been playing around with the Content Query Web Part along with CommonViewFields and styling with ItemStyle.xsl. I decided to leverage this functionality and the result was instant. I exported the Content Query Web Part and redeployed it under a different name to include the CommonViewFields of PublishingPageContent,HTML; and all fields associated with my ‘multi-part’ page layout. Important to note is that when used on the page and after pointing it to the relevant Pages library the page to copy exists in, an additional filter of Title equal to the current page’s title needed to be set – in hindsight I could have created a new web part overriding the content query web part to set this filter and the CommonViewFields automatically.

I then edited the ItemStyle.xsl file to include templates for all the fields listed in CommonViewFields:

<xsl:template name="PageHTMLOnly" match="Row[@Style='PageHTMLOnly']" mode="itemstyle">
    <xsl:variable name="DisplayTitle">
        <xsl:call-template name="OuterTemplate.GetTitle">
            <xsl:with-param name="Title" select="@Title"/>
            <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:value-of disable-output-escaping="yes" select="@PublishingPageContent"/>
</xsl:template>

Only 2 issues remained; firstly, all in-content links pointed to the non-optimised pages regardless if a mobile-optimised page existed and secondly, users weren’t being warned if they were being taken to an un-optimised page within the site. jQuery to the rescue.

To ensure mobile-optimised pages were linked to appropriately, I included the attribute class=”mobilelink” on all hyperlinks that needed it. The following jQuery took care of the rest:

$('.mobilelink').attr('href', function (index, val) {
  var returnVal = val;

  if (val.toString().toUpperCase() == '/CAREERPLANNING/KNOWINGYOURSELF/PAGES/KNOWINGYOURSELF.ASPX')
    returnVal = '/mobile/careerplanning/pages/knowingyourself.aspx';
  else if (val.toString().toUpperCase() == '/PAGES/CONTACTUS.ASPX')
    returnVal = '/mobile/contactus/pages/contactus.aspx';
  else if (val.toString().toUpperCase() == '/HOWWECANHELP/PAGES/HOWWECANHELP.ASPX')
    returnVal = '/mobile/pages/howwecanhelp.aspx';
  else
    returnVal = '/mobile' + val;

  return returnVal;
});

It would have been a little neater had the mobile site structure mapped more accurately to the live site structure, but never mind.

To ensure users were warned if they were about to enter a non-optimised page, jQuery Dialog was harnessed:

<div id="dialog" title="Confirmation Required">
  You are about to leave the mobile-optimised site.<br /><br />
  Would you like to continue?
</div>
$("#dialog").dialog({
  modal: true,
  bgiframe: true,
  width: 280,
  height: 185,
  autoOpen: false
});

$("a").filter(function (index) {
  var theHREF = $(this).attr("href");
  return theHREF != undefined &&
  theHREF != null &&
  theHREF != "" &&
  theHREF.toUpperCase().indexOf("JAVASCRIPT:") == -1 &&
  theHREF.toUpperCase().indexOf("/MOBILE/") == -1 &&
  theHREF.toUpperCase().indexOf("TEL:") == -1 &&
  theHREF.toUpperCase().indexOf("MAILTO:") == -1 &&
  theHREF != "#" &&
  theHREF.charAt(0) != "#";
}).click(function(e) {
  var theHREF = $(this).attr("href");
  var target = $(this).attr("target");
  e.preventDefault();

  $("#dialog").dialog('option', 'buttons', {
    "Confirm" : function() {
      if (target != undefined && target == "_blank")
      {
        window.open(theHREF, target);
      }
      else
      {
        window.location.href = theHREF;
      }

      $(this).dialog("close");
    },
    "Cancel" : function() {
      $(this).dialog("close");
    }
  });

  $("#dialog").dialog("open");
});

The end result was a mobile site that needed to be created manually initially (but with little effort – my experimentation with variations concluded that I would have had to do the same if not more to get that working initially) but would largely be set and forget assuming the mobile site structure didn’t need to change. Content updates would be instant rather than relying on the synchronisation process between the source and varied page. We were also able to maintain the ‘root’ URL of careercentre.dtwd.wa.gov.au by not requiring the source variation site to be created.

The last remaining problem was getting there. Seeing we hadn’t harnessed variations, we didn’t have the VariationRoot page to direct browsers to the mobile version of the site. Instead I ensured the default page of the root site had a web part on it that contained the logic to perform the redirect courtesy of Detect Mobile Browsers.

So there we have it. A result not particularly conforming with stated best practices however one the client is stoked with and should prove easily maintainable and effective. I’d be particularly interested to hear about other ways in which mobile sites have been created in SharePoint and anyone’s experience using SharePoint variations for that purpose.