Tuesday, 18 February 2020

How to append text in a document library

I recently needed to create a Document Library in SharePoint Online with a text field that could append blocks of text against each document to track progress of several workflows. Of course, the first task is to check Google to see whether there was a workaround for not being able to select an "Append" option on the Multiple lines of text field in a Document Library. After about an hour of trying different search terms I had to conclude that there wasn't an easy, out-of-the-box way of doing this. But there's always a workaround, right?

The secret lies in workflow variables.

But first, create a Multiple lines of text field. I called mine "Actions Log", because I'm logging the workflow actions. Then you'll need to use either an existing workflow or - if append text is the only action you need to take - create a new workflow.

Within the workflow, you're going to need two variables. One to hold any existing text in your Multiple lines of text field and one to hold a copy of the original text plus any appended text.

To create a Workflow variable, you need to first select "Set Workflow Variable" from the Actions drop-down.
So create your first workflow variable ... I've put some conditions in my workflow, but of course you don't have to. Click on the Action drop-down and find Set  Workflow Variable. If you can't see that option, start typing the text "set workflow variable" and it should appear.

Having selected "Set Workflow Variable" you're now prompted to create a new variable.
I'm calling my new Variable "ActionLog" (dropping the "s" so I can easily distinguish between the two), and I'm setting (content) Type to "String".

Naming the new variable and setting the content type are both done in the same dialogue box.
Next, set the Variable to hold the contents of the ActionsLog field. This will copy the existing text - if any - in the ActionsLog field to the ActionLog variable.

This step causes the value in the Action Log text column to be copied into the ActionsLog variable.
Now you're ready to create your second variable. I called this one ActionsLogAppend, also set as a string. So just repeat the above steps ...

Create a second variable called ActionsLogAppend which will hold the original Action Log text and any new appended text. 
Now you're ready to build the actual machinery of the Append Text function. In the example here, I'm having the Workflow append predetermined text into the existing Multiple Lines of Text field. And I do that by opening up the Parameter Builder to create a value for the second variable.

Click on the ellipsis to launch the String Builder window, which in turn creates the ActionLogAppend value.
Building the value for the variable is pretty simple. I wanted my appended text to look like this:

20/11/2019: 10.55 - 18 months to End of Lease acknowledged by John Smith:
[Content of first variable]

So in the String-Builder, add a lookup for the Date (and the Time, if you want it), and add the identity of the person who carried out the action (that'll be the value for ModifiedBy).

I set the Date and Time to "Short Date" and "Short Time" content types, but you may prefer a different setting.
Then I just copied the combined text of the two variables into the Actions Log variable of the Workflow.

This is what the string looks like in the String Builder window. I put a line return after the ModifiedBy call for clarity, putting the appended text on a new line ...
The final String in the String-Builder would look like this - 

[ModifiedDate]: [ModifiedTime] - 18 months to End of Lease acknowledged by [ModifiedBy]:
[Variable:ActionLog]

But if you wanted to, you could copy this combined text back into the Multiple Lines of Text field via the workflow.

That's it ... I hope this helps someone.


Tuesday, 6 August 2019

Can’t use Calculated Column to generate a URL?

ANYBODY WHO HAS WORKED WITH SHAREPOINT ONLINE will know that Microsoft love to turn off functions we rely on. A recent casualty was the facility to use List Calculated Columns to concatenate a URL to a customised Display or Edit Form.


Oh, how I love to concatenate. And how sad I was when Microsoft decided
they didn't like me concatenating links.
How we would have done it in the past would be to create a Calculated column in a list, then add a formula like this:

=CONCATENATE("<A HREF='/sites/SiteName/Lists/ListName/DispForm00.aspx?ID=",[ID]'>Display Form</A>")

Or if you wanted to be really smart, you could include a redirect to take your user to a particular destination after they’d finished with the form, like this:

=CONCATENATE("<A HREF='/sites/SiteName/Lists/ListName/DispForm00.aspx?ID=",[ID],"&Source=https://xxx.sharepoint.com/sites/SiteName/Lists/ListName/'>Display Form</A>")

The problem is that Microsoft have now disabled that functionality, so that while the Calculated column will compile the link, the list will display the HTML line of code, and not a link as you intended.

I went trawling around Google, looking for a workaround for this “undocumented feature” and came up empty. There’s quite a few folks who suggest many different JavaScript and JQuery solutions, but I couldn’t get any of them to work. JavaScript is okay and can solve some of your problems in SharePoint, but I can’t help feeling it’s a bit of a hack. Far better to use in-built SharePoint technology to solve SharePoint problems where possible.

So I resolved to come up with a workable solution myself, something simpler than fiddling around with a JavaScript function that would magically transform a string of HTML into a working link.

Everything was pointing to a workflow.

I set one up to copy the calculated HTML code into a simple text column, but of course that didn’t work.

My next attempt was to copy the concatenated string into a Hyperlink column. That kind of did work, but displayed both the link URL and the link text as the full line of HTML … and it seemed to be clickable, but it didn't quite work as I thought it would.

Even though it appeared to produce a functioning link, the "gotcha"
was that the call to the item's ID didn't work, so I had a rethink.
But that roadblock led me to a simpler, more elegant solution anyway ... use a Workflow Variable to compile and copy the URL string into a Hyperlink column.

From there, it was a straightforward task to fire up SharePoint Designer and add a simple workflow to copy the contents of a Variable to your Hyperlink Column.

In your new Workflow - I used a 2010 Workflow - under Actions, select "Set Workflow Variable".

Add your first action ...
In the following window, click on Workflow variable and select 'Create a new variable". Give a useful name and select "String" for the Type. The click on Value, and select the ellipsis to open the String Builder.

Make sure you set up the URL string in a format
that the Hyperlink column will accept.
The string should be in this format:

https://xxx.sharepoint.com /sites/SiteName/Lists/ListName/DispForm00.aspx?ID=[ID]&Source=https://xxx.sharepoint.com/sites/SiteName/Lists/ListName/, Display Form

Replace the "[ID]" in the above string with the "Current Item:ID" value from the Add or Change Lookup function.

The comma and the word space after the URL (but before the link text) is vital to ensure the string transfers to the Hyperlink field correctly.

I also set up my Workflow to check whether the Hyperlink Column was populated then, if not, copy the contents of the Variable into the Hyperlink Column, like this:

Having the Condition  place to check whether the Hyperlink column is populated or not
saves triggering the whole workflow process unnecessarily.
Then set the Workflow to trigger when a new item is created and that should be it.

This is how you want your Hyperlink column to look after the Workflow has done its job.
I hope this has helped someone.



Friday, 16 November 2018

Add File Name field to custom EditForm

It's been a while since I posted here. Maybe this is because I've now switched to using SharePoint Online, and I'm finding it needs fewer workarounds than SP2007.

Okay ... that sounds a bit unlikely.

So here's a challenge I had recently. One of my clients asked that they get a File Name edit field in a custom EditForm I'd created for a Document Library.

The out-of-the-box EditForm included a File Name field as standard.

Here's an example of how a standard EditForm presents its fields. Right at the top, labelled "Name" is the file name field. We can tell it's for a PDF, as Microsoft has helpfully rendered the file suffix outside the editable field.

It seems that Microsoft disable the File Name field in custom EditForms. So I Googled to find a solution. The only workaround I came across suggested that the File Name field could be added manually by pasting some code into the EditForm in SharePoint Designer.

<asp:TextBox runat="server" id="ff26{$Pos}" ControlMode="Edit" text="{@FileLeafRef}" __designer:bind="{ddwrt:DataBind('u',concat('ff26',$Pos),'Text','TextChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@FileLeafRef')}"/>

Except that this doesn't quite work.

Let me explain.

If you use the above code - pasted in to the second table cell in the new row you'd create to hold it - you get this result:

Spot the difference - this version, the custom EditForm with the above code added doesn't render the file suffix. A bit confusing for end users, I'm thinking.
For me this was a bit of a deal-breaker. Without the visual hint that the file suffix gives the end-user, I didn't think it was obvious what that editable field was doing. Even more alarming, end users might change the file name without realising what they were doing.

This would explain why Microsoft chose to exclude this feature from the custom forms. Except, in this instance, my client needed it.

So I took a look at some of the other fields in the code and figured out a solution. Well, more of a lucky guess, really.

But first, let me say this. Look at the ID value in the above code: "ff26". SharePoint uses these values to differentiate between input fields. If you have two fields with the same ID, you will get an error: "Unable to display this web part ..." or similar.

So first make sure that before you add any code to the EditForm page you've scrolled to the end of the form fields section and checked the ID value of the last row in the form. In my case, it had the ID value of ff25.

Then, because I could see that the code for all the other form fields began with:

<SharePoint:FormField ...

I thought I'd try that instead, and re-wrote the above line of code as this:

<SharePoint:FormField runat="server" id="ff26{$Pos}" ControlMode="Edit" FieldName="FileLeafRef" __designer:bind="{ddwrt:DataBind('u',concat('ff26',$Pos),'Text','TextChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@FileLeafRef')}"/>

Be aware that the attribute "text" isn't permissible in the SharePoint:FormField tag, so I used FieldName instead.

And it worked.

The File Name field rendered perfectly and even tagged on a file suffix after the text field.


Success ... those small alterations to the code cause the custom EditForm to render in exactly the same way as the out-of-the-box one.
I hope that's helped someone ...





Friday, 22 April 2016

Add an "Out of date" alert to SharePoint content pages

I was at an Intranet Experts conference last year in Geneva, courtesy of Advatera. During the day, the subject of how we can get Site Builders in a federated content model to ensure they keep their content up to date. One suggestion was to add a banner to the top of any page that hasn't been updated within a certain time period, for example, a year.

Trouble is, no one really knew how to do it.

So I had a bit of think about it and figured that if I could come up with some way to retrieve the page's Modified value and put that into a JavaScript variable, then it would be easy to get a message to display on the page ... say, like this.



So ... how do you do it? The key is in the SharePoint control:

<SharePointWebControls:FieldValue ID="Modified" FieldName="Modified" runat="server"/>

So, with a lot of help from my colleague David T., this is what we came up with:

<script>
   $( document ).ready(function() {
      var shareDate = '<SharePointWebControls:FieldValue ID="Modified" FieldName="Modified" runat="server"/>';
      var regex = /(\d+)/g;
      var dateParts = shareDate.match(regex);
      var docDate = new Date(dateParts[2]+"/"+dateParts[1]+"/"+dateParts[0]);
      var prevDate = new Date();
      prevDate.setDate(prevDate.getDate() - 365); //This is the number of days in the past to check

      if(docDate < prevDate){
      $( ".Breadcrm" ).append("<strong class='OutOfDate'>The content on this page has not been updated in over a year and may be unreliable</strong>");
}
   });

</script>

To get this work cleanly, I inserted the script at the end of the <div> block that holds the breadcrumb trail in the content page's MasterPage (see the screengrab above for what the final (active) alert looks like.

It's not a foolproof method, because all a content editor has to do is Edit the page then immediately Publish it and the Alert will disappear. But at least it will concentrate the attention of the more diligent content editor.

Hope this helps someone.

Monday, 14 March 2016

Create dynamic list forms with jQuery and SP Designer

In an earlier post, I covered ways to make certain fields in a SharePoint list's input forms invisible, using JavaScript. But recently, I had a request to make input fields appear based on the user selecting a value from a picklist (drop-down list). With quite a bit of help from my colleague David T, I managed to figure out a way to create quite dynamic input forms for a SharePoint list using jQuery. This is how we did it.

You will need SharePoint Designer access in order to do this. If you don't have SharePoint Designer access then this method will not be possible for you.

So, this is what we'll end up with. When the user wants to create a new item, the NewForm.aspx page will come up looking like this:


Select a category and the dynamic input form will change.
The user will then select the category they want and the form will display differently, depending on the category chosen. In this case, the list was designed to hold Service Alerts and Planned Outages for IT systems within our company. Service Alerts happen on an ad hoc basis and don't have Start or End dates. 


This is how the form displays if you choose "Service alert".
But Planned Outages, by definition, will have a Start and an End date. Thus choosing Outage will reveal the fields for the Beginning and End of the Planned Outage.


However, Outages require an Start Date/Time and and End Date/Time ...
So, in the browser, set up your list with all the fields you need for either (or all, if you're having more than two choices) situation, and be sure to include a Category field as a Choice, so you can switch between the options in the NewForm.aspx. I started with an Announcements list, as this was essentially a news feed, and customised it slightly.


Here's the Announcements list with the added custom fields.
Make sure you have the columns in the order you want them to appear in the NewForm.aspx. If you don't, and you change the order later, this will affect the way the jQuery script works and you'll end up with a broken form.

Next, create a document library in your site, and name it "siteJS". In that, place a blank Notepad document, named "NewForm.js". You'll use this later to hold the script that controls the NewForm.aspx. 

Now, fire up SharePoint Designer and navigate to the Site where you created the List. In the Folder List, find the Lists section and expand it. Now find your target list, and expand that.


This is where all the out-of-the-box list forms are stored.
Find the NewForm.aspx under the List and open it. If you get an alert box asking if you want to check the page out, click Yes.

Now somewhere around line 15 in the code for the page, you should see a line that says:

<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">

Under that, you'll place your calls to the jQuery files. I prefer to store the jQuery files locally (as you can see from the paths in the screengrab). But you can use calls to the remote jQuery files if you want - a quick Google search will throw up a slew of examples.


This is what the code looks like in SP Designer. Make sure you put your JavaScript in the right place or the function won't work.
Once you done that, you can also add a call to your blank Notepad document you saved earlier.


Add the call to the text file that will hold your custom JavaScript. Relative pathways are fine.
Now, we're more or less ready to start on the real meat of this task. But first, you'll need to fetch the ID's of all the fields in the NewForm.aspx page. You can do this most effectively by bringing the page up in your browser then Viewing Source. Each field will have an ID associated with it. These IDs also reflect the order of the fields in the NewForm page. If you change the order of the fields, the IDs will change. Use Find to speed though the source code of the NewForm page.

It would save you time if you load each ID into the NewForm.js file as you go along, as this is where they'll end up. I prefer to use NotePad++ over regular Notepad, but either will do.  So, open the NewForm.js file and begin entering the field IDs as you copy them from the View Source window. To save yourself a little time, enter them in this format:

var trBody = $("#ctl00_m_g_6c609f0f_5ff0_49e3_b8da_0653643109f9_ctl00_ctl04_ctl04_ctl00_ctl00_ctl04_ctl00_ctl00_TextField").closest("tr");  // Field = Body Text

You're setting each field up as a variable, which you can use later in the process to control the visibility of the field. For text fields, the above format is fine, but date fields are trickier to manage, because date fields are made up of more than one component. So they require two variables to be set to manage them correctly. I set up the date fields variables like this:

var trExpires = $("#ctl00_m_g_6c609f0f_5ff0_49e3_b8da_0653643109f9_ctl00_ctl04_ctl08_ctl00_ctl00_ctl04_ctl00_ctl00_DateTimeField_DateTimeFieldDate").parent().parent().parent().parent().parent().parent().parent(); // Row = Expires 
var fncExpires = $("#ctl00_m_g_6c609f0f_5ff0_49e3_b8da_0653643109f9_ctl00_ctl04_ctl08_ctl00_ctl00_ctl04_ctl00_ctl00_DateTimeField_DateTimeFieldDate").closest("tr");  //Field = Expires

The first variable above controls the tables row that the field appears in, and the second variable controls the date function itself.

When you have all the field IDs added in this way, you're ready to add the rest of the scripting.

Before your list of field variables add this scripting:

$(document).ready(function() {
// *** START HIDE-SHOW FIELDS BASED ON "CATEGORY" DROP-DOWN SELECTION ***

// FORM FIELD VARIABLES
var selectList1 = $('#ctl00_m_g_6c609f0f_5ff0_49e3_b8da_0653643109f9_ctl00_ctl04_ctl01_ctl00_ctl00_ctl04_ctl00_DropDownChoice'); // Field = Select category

Commenting is good practice that helps you keep track of what's going on. Notice that I put the Category drop-down variable at the top of the listing of variables and named it slightly differently from the others.

Now ... below the list of variables, add this script:

//resets the form
function clearAll() {
  $('*[id*=5ff0_49e3_b8da_0653643109f9_ctl00_ctl04]:visible').each(function() {
      $(this).closest("tr").hide();
      });
      $(selectList1).closest("tr").show(); //show dropdown
      $(trStartDate).hide();  // Field = Start Date
      $(trEndDate).hide();  // Field = End Date
      $(trExpires).hide();  // Field = Expires Date
}
clearAll(); // hides all rows upon loading the page

This hides all the fields except for the Category drop-down. The line that begins $('*[id*= finds every field with an ID that contains "5ff0_49e3_b8da_0653643109f9_ctl00_ctl04" and the next line hides them all. The next four lines that begin with "$" shows the drop down and hides the table rows that contain the date fields.

Now, beneath the above script, add this:

//Category > Alert
function alert() {
clearAll(); // call the Alert form function
$(trCategory).show();  // Field = Category
$(trTitle).show();  // Field = Title
$(trStatus).show();  // Field = Status
$(trIncident).show();  // Field = Incident number
$(trBody).show();  // Field = Body text
$(trBodyTool).show(); // show text field options
$(trUpdate).show();  // Field = Update Test / Reason
// Next two lines required to make Expired date field show
$(trExpires).show();  // Row = Expires Date
$(fncExpires).show();  // Row = Expires Date
}

This block of script makes the required fields for the Alert category visible.

Next, add this script, which makes the other category (Outage) visible:

//Category > Outage
function outage() {
clearAll(); // call the Outage form function
$(trCategory).show();  // Field = Category
$(trTitle).show();  // Field = Title
$(trStatus).show();  // Field = Status
$(trIncident).show();  // Field = Incident number
$(trBody).show();  // Field = Body text
$(trBodyTool).show();  // show text field options
$(trUpdate).show();  // Field = Update Test / Reason
// Next two lines required to make Start date field show
$(trStartDate).show();  // Row = Start Date
$(fncStartDate).show();  // Row = Start Date
// Next two lines required to make End date field show
$(trEndDate).show();  // Row = End Date
$(fncEndDate).show();  // Row = End Date
// Next two lines required to make Expired date field show
$(trExpires).show();  // Row = Expires Date
$(fncExpires).show();  // Row = Expires Date
}

Finally, add the block of script that controls the whole function:

// calls function if selectlist1 is changed
$(selectList1).change(function() {
var funcCall = "";
var primarySelect = $(selectList1).val().toLowerCase().replace(/-|\s/g, '');
var funcCall = primarySelect;
//alert("Segment: "+ primarySelect +"\nRequestType: "+secondarySelect +"\nFunction Name: "+funcCall);
eval(funcCall+'()');
});

You don't need the Alert line. I was using this for testing a version of this script that had two different category drop-downs.

Finally, make sure the whole script block is closed by adding:

});

Save the file, and you're ready to test.

That should be it. Hope this helps someone.

Thursday, 28 January 2016

Use DDWRT to format your dates

The ddwrt date functions in SharePoint are not the easiest things to get to grips with. And it's quite difficult to find a definitive list of all the functions When I have found lists in other people's blogs, the functions posted don't always coincide with what I'm seeing in my site collection. This might be a result of us all using slightly different builds of SharePoint, but I'm going to list the ddwrt variations that I have observed on my SharePoint Site Collection by actually using them.

If you are going to use ddwrt date formatting in your Data View Web Part, you must include the line:

xmlns:ddwrt=http://schemas.microsoft.com/WebParts/v2/DataView/runtime

... inside the <xsl:stylesheet ...> tag, like this:

<xsl:stylesheet 
   version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" 
   xmlns:x="http://www.w3.org/2001/XMLSchema" 
   xmlns:dsp="http://schemas.microsoft.com/sharepoint/dsp" 
   xmlns:asp="http://schemas.microsoft.com/ASPNET/20" 
   xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
   xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
   xmlns:SharePoint="Microsoft.SharePoint.WebControls" 
   xmlns:ddwrt2="urn:frontpage:internal"
   xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime">

The 1033 LCID is used for US date formatting and the 2057 LCID is for UK date formatting. Where there's a hyphen in the Result column below, it means that the relevant ddwrt value produced no result for me.

DDWRT valueResult
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,1)" />2/21/2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,2)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,3)" />Sunday, February 21, 2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,4)" />1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,5)" />2/21/2016 1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,6)" />1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,7)" />Sunday, February 21, 2016 1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,8)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,9)" />2/21/2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,10)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,11)" />Sunday, February 21, 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,12)" />1:00:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,13)" />2/21/2016 1:00:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,14)" />1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,15)" />Sunday, February 21, 2016 1:00:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,16)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,17)" />2/21/2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,18)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,19)" />Sunday, February 21, 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,20)" />1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,21)" />2/21/2016 1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,22)" />1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,23)" />Sunday, February 21, 2016 1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,25)" />2/21/2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,27)" />Sunday, February 21, 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,28)" />1:00:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,29)" />2/21/2016 1:00:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,30)" />1:00:00 PM
DDWRT valueResult
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,1)" />21/02/2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,2)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,3)" />21 February 2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,4)" />13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,5)" />21/02/2016 13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,6)" />13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,7)" />21 February 2016 13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,8)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,9)" />21/02/2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,10)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,11)" />21 February 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,12)" />13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,13)" />21/02/2016 13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,14)" />13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,15)" />21 February 2016 13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,16)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,17)" />21/02/2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,18)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,19)" />21 February 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,20)" />13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,21)" />21/02/2016 13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,22)" />13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,23)" />21 February 2016 13:00
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,25)" />21/02/2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,27)" />21 February 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,28)" />13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,29)" />21/02/2016 13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,30)" />13:00:00

If you're using the FormatDateTime specifier, it doesn't matter whether you define the LCID as 1033 or 2057.

DDWRT valueResult
<xsl:value-of select="ddwrt:FormatDateTime(string(@EventDate),1033,'dd-MMM-yy')" />21-Jan-15
<xsl:value-of select="ddwrt:FormatDateTime(string(@EventDate),1033,'dd/MM/yy')" />21/01/15
<xsl:value-of select="ddwrt:FormatDateTime(string(@EventDate),1033,'dd/M/yy')" />21/1/15
<xsl:value-of select="ddwrt:FormatDateTime(string(@EventDate),1033,'ddd, dd/M/yy')" />Sun, 21/1/15
<xsl:value-of select="ddwrt:FormatDateTime(string(@EventDate),1033,'dddd, dd/M/yy')" />Sunday, 21/1/15

The full list of specifiers is as follows:

SpecifierResult
dDay of the month, single digit 1-9, double digit 10 up
ddDay of the month, leading zero 1-9, double digit 10 up
dddAbbreviated day of the week, ie, Wed
ddddFull name of the day of the week
Specifier Result
MThe month, 1 to 12
MMThe month, 01 to 12
MMMAbbreviated name of the month
MMMMFull name of the month
Specifier Result
yThe year, from 0 to 99
yyThe year, from 00 to 99
yyyThe year with a minumum of three digits, ie 900
yyyyThe year as a four-digit number

You can also add the following, if you want to display the time, as well:

Specifier Result
h12-hour clock, 1 to 12
hh12-hour clock, 01 to 12
H24 hour clock, 1 to 24
HH24 hour clock, 01 to 24
mMinutes, 0 to 59
mmMinutes, 00 to 59
sSeconds, 0 to 59
ssSeconds, 00 to 59

Use anything you like to separate the components ... a dash, a slash or wordpsaces and/or commas.

Hope this helps someone.