Monday, 17 December 2012
Fun With PowerShell and SharePoint 2010
Friday, 16 November 2012
SharePoint Page View Counter
The Masterpage
The List
The Web Control
Conclusion
Thursday, 15 November 2012
SharePoint Hit Counter
| Fig.1 - Hit Viewer web part |
![]() |
| Fig.2 - Hit Counter web part |
![]() |
HitCounter Web Part
HitViewer Web Part
In Closing...
Thursday, 21 July 2011
SharePoint Dataviews - Items Per Row?
While DVWPs (Dataview Web Parts) undoubtedly rock, styling them has always been a nightmare of wading through line after line of inexplicable XSLT. To make matters worse, MOSS currently only supports v1.0 and uses the DDWRT namespace.
This means you can't rely on many of the online tutorials and examples you might come across when it comes to styling this beast.
Thankfully, SharePoint Designer provides many useful UIs to assist in this area but IMO they still fall short when it comes to something as simple as stating HOW MANY ITEMS PER ROW you might like to display. Which, of course, brings us finally to the point of this post. :)
The plan is to go from this:
| Name | Title | Descr |
|---|---|---|
| Item1 | First Item | Dull |
| Item2 | Second Item | Drab |
| Item3 | Third Item | Boring |
| Item4 | Fourth Item | Kill me now |
To something like this:
First ItemWow! | Second ItemGreat stuff! | Third ItemTerrific! | Fourth ItemWizard! |
In my search for a solution to this problem I came across a few examples but sadly none of them was able to provide a straightforward example of how to achieve this. To be fair to Microsoft, there is a two column layout which is what provided me with the base code. But it is terribly inflexible and doesn't allow for simple variations. So, pencils at the ready. Here we go...
Let's Get Started
First things first, you want to either convert an existing List View or add a new Dataview from the Insert menu. Add or remove data fields and header rows as you like; apply pagination, limits, sort order...just don't group by anything. That would add an additional complication to what we're trying to achieve.
For the purpose of this exercise accept the default table template as well. i.e. Don't change the layout.
In Source view, look for the opening
<xsl:template match="/"...
Breaking Things Down
For the sake of this exercise I will be styling an image library view but the steps will apply to any type of list. The first thing we're going to do is reduce the meat of this template down to its bare essentials like so:
<table id="myID">
<xsl:call-template name="dvt_1"/>
</table>The reason for the ID should be obvious. Apart from providing an easy method for applying custom styles, it also facilitates targeting the table with jQuery or any other language. That's our first template down. Now let's move down to the next two for the body. You may not need to make any changes to these but I provide the code below just in case.
<xsl:template name="dvt_1">
<xsl:variable name="dvt_StyleName">Table</xsl:variable>
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
<xsl:call-template name="dvt_1.body">
<xsl:with-param name="Rows" select="$Rows"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="dvt_1.body">
<xsl:param name="Rows"/>
<xsl:for-each select="$Rows">
<xsl:call-template name="dvt_1.rowview"/>
</xsl:for-each>
</xsl:template>Using Custom Templates and Variables
Now because this is an image library I've created an additional template to define the thumbnails. While this won't be relevant for other lists I include it as an example of how you can easily create your own custom variables and see how they can be applied. Skip this if you already know.
<xsl:template name="getThumb">
<xsl:param name="str"/>
<!-- Default file type for MOSS preview pics -->
<xsl:variable name="ext">.jpg</xsl:variable>
<xsl:choose>
<xsl:when test="contains($str, $ext)">
<xsl:value-of select="substring-before($str, $ext)"/>
<!-- Default suffix for MOSS preview pics -->
<xsl:text>_jpg.jpg</xsl:text>
<xsl:call-template name="getThumb">
<xsl:with-param name="str" select="substring-after($str, $ext)"></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$str"></xsl:value-of>
</xsl:otherwise>
</xsl:choose>
</xsl:template>Now For the Fun Part
Now we need to define the contents of our rows. While this looks needlessly complicated (it is!) I've included comments to help break it down. If it still doesn't make sense, don't worry about it. It works. Trust me, ok? ;)
You can either modify the existing template, which will already contain your preferred data fields, or delete everything and start from scratch. You can, of course, omit the imageThumb variable and just start with the opening table cell.
<xsl:template name="dvt_1.rowview">
<!-- Create a variable to hold our thumbnail template. -->
<xsl:variable name="imageThumb">
<!-- Call our template -->
<xsl:call-template name="getThumb">
<xsl:with-param name="str" select="@NameOrTitle"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<!-- START HERE!!
Now we define our row. You can define this content any way you want
within the confines of the table cell. Just use the appropriate
field names and layout for your list. -->
<td>
<!-- Title field -->
<h4><xsl:value-of select="@Title"/></h4>
<!-- Create a hyperlink to the item display form and wrap it
around our thumbnail. -->
<a href="{@FileRef}"
title="{@Title}: {@Description}"
name="{@Title}">
<img src="/{@FileDirRef}/_t/{$imageThumb}" alt="{@Description}" />
</a>
</td>
<!-- This is where the magic happens. We use the mod function to define how
many items we'd like per row. We then test for our position (item
count)in the data array. If it's not a multiple of our number then
we ignore it and continue rendering our item template. If it is,
we close the row and start a new one. -->
<xsl:if test="position() mod 4 = 0" ddwrt:cf_ignore="1">
<xsl:text disable-output-escaping="yes"></tr></xsl:text>
<xsl:if test="position() != last()" ddwrt:cf_ignore="1">
<xsl:text disable-output-escaping="yes"><tr></xsl:text>
</xsl:if>
</xsl:if>
<!-- Don't forget to apply your item count here too! -->
<xsl:if test="position() = last()" ddwrt:cf_ignore="1">
<xsl:if test="position() mod 4 != 0" ddwrt:cf_ignore="1">
<!-- This never seems to gets called but is somehow required anyway! -->
<span></span>
</xsl:if>
</xsl:if>
</xsl:template>
And that's it! Hope you found this useful.
Tuesday, 20 October 2009
SharePoint Advanced Search Properties Don't Work - the skinny on Created By, Modified By, Author and more
What's not Broken?
Despite the claims of many, the Size and (Created/Modified)Date properties are not completely broken. They simply require the correct format for input. It's also worth noting that the explicit value is required when using property searches. No wildcards or partials!- Size - takes a value in bytes, so that 1000000 = 1Mb.
- Dates - require a xx/xx/xxx or xx/xx/xxxx format. The order of the days/months will depend on your regional settings. If you're still getting no results, then check the metadata mappings and XSLT references below.
What is Broken and Why?
As many have discovered, the Created By, Last Modified By, Created Date, Last Modified Date and Author properties are all affected to some degree.The root cause for all this is down to improper mapping of crawled properties to their managed properties within Search Administration and keeps going all the way through to the XSLT for the Advanced Search and Search Core Results web parts.
The reasons behind these poor relationships become obvious shortly after you begin looking for a solution. To be frank, it also becomes obvious why most people gave up trying!
Thanks to...
Much thanks goes Anne Stenberg and her 6-part series entitled - Mystery Solved - Crawled Properties in SharePoint.In this series Anne patently and painstakingly goes through every last property in each defined category, and providing a description for many. I'm not entirely sure where she came by all this information but it proved invaluable when it came to identifying and testing the result of many changes to come in my metadata property mapping.
Please explain!
Using a combination of Anne's tables, the U2U CAML Query Builder feature, the ever useful SharePoint Manager, and the XSLT within the search web parts - it quickly becomes obvious that it's going to take more than a packet of off-the-shelf headache tablets to get through this.Without going into too much detail - ignorance being bliss - let's take a look at something as simple as Author.
- We have a visible Author column whose internal name is _Author.
- A hidden Created By column whose internal name is Author.
- And a managed property called Author that seems to want to hedge its bets by trying to cover all these bases as well as a few more.
What's the fix already!?
OK, OK. Keep your propeller hat on.After days of stuffing around, tweaking mappings, modifying web part properties and performing a full crawl each time(!) I have finally found - I think - a solution. At least, a number of searches - using Author, Created By and Last Modified By properties with the AND operator - all returned correct results.
It's also worth noting that this solution is not Office-centric and will work with any document type.
First, the Metadata
This assumes a good knowledge of Central Administration. If you require detailed steps they can be fond elsewhere.You can use all or some of the settings shown below but the only ones that really matter are the Mappings themselves. After you've added the crawled properties, be sure to click each one and check the "Include values for this property in the search index" checkbox, otherwise it won't get added to the index! In all cases I went with the default "Inlcude values from all crawled properties mapped" option.
Also note that there are often TWO properties with exactly the same name - e.g. Office:4(Text). Picking the right one is essential and I have provided the Property Set IDs below where this is relevant.
And, remember, what follows is in no way Gospel - it's just what worked for me.
NB: Don't forget to run a Full Crawl after making these changes.
Property Name | Type | May be deleted | Use in scopes | Mappings |
|---|---|---|---|---|
| Author | Text | No | Yes | _Author(Text), ows__Author(Text) |
| Created | Date and Time | Yes | No | Office:12(Date and Time), Basic:15(Date and Time) |
| CreatedBy | Text | Yes | No | Office:4(Text), ows_Created_x0020_By(Text |
| LastModifiedTime | Date and Time | No | Yes | Basic:14(Date and Time), Basic:16(Date and Time), ows_Modified(Date and Time) |
| ModifiedBy | Text | Yes | Yes | Office:8(Text) |
- Office:12(Date and Time) - f29f85e0-4ff9-1068-ab91-08002b27b3d9
- Basic:15(Date and Time) - b725f130-47ef-101a-a5f1-02608c9eebac
- Office:4(Text) - f29f85e0-4ff9-1068-ab91-08002b27b3d9
- Office:8(Text) - f29f85e0-4ff9-1068-ab91-08002b27b3d9
Advanced Search XSLT
The following go in PropertyDefs. There are many default values here, I'm just providing the full block. You'll then need to add the same 'Name' references to each ResultType in the order you prefer.<propertydef name="Author" datatype="text" displayname="Author">
<propertydef name="Size" datatype="integer" displayname="Size">
<propertydef name="Keywords" datatype="text" displayname="Keywords">
<propertydef name="CreatedBy" datatype="text" displayname="Created By">
<propertydef name="Created" datatype="datetime" displayname="Created Date">
<propertydef name="ModifiedBy" datatype="text" displayname="Last Modified By">
<propertydef name="LastModifiedTime" datatype="datetime" displayname="Last Modified Date">Search Core Results XSLT
Unless you're trying to provide custom results using some of the values described above you won't need to make any changes here. Quite frankly it's a little daunting but great things can be done - such as displaying Size, Author and a custom link to open the containing folder for each result. I'll probably leave this for another post as it's a topic in itself.In conclusion...
So, hopefully, if you've done everything right and performed a full crawl, you should now be able to search using one or all of the properties we've discussed here.One thing you may find still doesn't work is the "Does not equal" operator. You might also that it's not described anywhere in the web part code but is managed by a separate core JavaScript file. I'm just not willing to look into this right now - or the reasons why "Contains" and "Doesn't contain" aren't available for partial search term querying. If anyone else has any ideas - performance notwithstanding - feel free to drop me a line.
I look forward to any further insight and feedback others might have and hope that all my hard work isn't undone with the next upgrade!
Thursday, 16 July 2009
Deleting unused SharePoint Content Types
It happens to every site collection admin at some stage. For whatever reason you're required to replace a defunct content type in your document library with a new one. Adding t he new one is easy. But then you try to delete the old one and receive the terrifically informative "Content Type is still in use" error.
You've tried everything:
- Updated any files you could find with the new content type.
- Checked every file twice to make sure you didn't miss any.
- Managed Checked Out files to locate all those tricky 'hidden' docs that haven't been checked in yet and only exist in some funky temp/draft state! (You'll either need to take ownership of these as site collection admin, or email a list to the original authors to get them to check them in or delete then. I can't believe there is no option to delete these en masse!)
- Run custom CAML queries to make REALLY sure you didn't miss any (using the fabulous U2U SahrePoint CAML Query feature - www.u2u.be/res/Software.aspx.
- Written a console app just to make ABSOLUTELY sure you didn't miss any.
- Emptied the recycling bin - both of them!
But still the error persists.
Just when I thought I'd exhausted all options it occurred to me that perhaps versions were the culprit. Looking at the version history for a few suspect documents confirmed that they were.
Running the following SQL query will find them all.
SELECT *
FROM AllUserData
WHERE (tp_DirName LIKE '%sitename/Shared Documents%')
AND ((tp_ContentType = 'myDodgyCT'))
ORDER BY tp_DirNameThis should be self-explanatory but tp_dirName is just using a relative path from the domain to the library. And ContentType is the explicit name of the content type to search on.
From there I just exported the results to Excel, filtered out duplicates and was left with a workable list of documents. Simply publish a final version of each (if required), then go to the Version History and delete all previous versions.
You could do all this in one step by turning off versioning for the library and then turning it on again. But this would delete versions for ALL documents. A good trick to remember when you're site quota is reached.
Good luck!
Thursday, 18 June 2009
Import Publishing Sub Site as Site Collection
The obvious solution would be to export the site(s) in question and then reimport them to an empty site collection. Yes, that would seem obvious, but it's not quite as straightforward as you might assume. What really compounds the matter is if you happen to have enabled the publishing feature on your source site.
After many days of disappointment and incoherent error messages I finally returned to Gary Lapointe's brilliant blog - SharePoint Automation. Gary has developed dozens of custom stsadm commands in order to fill the cavernous and aching void left by the default offerings. One thread in particular, Subsite to Site Collection, deals with this pesky issue and also includes his own hair-pulling frustration with the out-of-the-box limitations to achieve this. BTW, the solutions he provides are not strictly supported - but they work where nothing else will and are very easy to use.
In my case I simply deleted every failed test and started from scracth using the gl-convertsubsitetositecollection operation. All I had to do was provide the source site and destination URL and the command did the rest.
This exports the source site, creates the destination site collection and site (based on source site template), and activates any features. The source site was a Team Site with publsihing and several custom features enabled. The only thing I did was create the managed path but this command even has a flag for that!
The only thing it doesn't appear to do (yet) is allow you to specify a new content database. I've already put my request in for this and hope to see it soon. If this helps someone, please thank Gary - not me! :)


