Microsoft SharePoint is an enterprise platform with a long history and vast variety of features, which is why it can’t always react quickly enough to follow emerging Web technology trends. Despite a wide enterprise adoption of SharePoint and a huge effort to provide a broad number of features, SharePoint still lags behind modern CMS products in terms of immersive UIs, such as HTML5 and CSS3.
In my opinion, HTML5 is not only a hot new technology, but it truly has many practical benefits: it’s easy, convenient and rich—and it’s supported, more or less, by all the modern browsers (including mobile device browsers). Additionally, HTML5 and JavaScript are becoming major technologies for desktop programming in Windows.
So HTML5 definitely deserves its place in SharePoint to make portals much easier to use. And improving SharePoint interfaces can really help business users by enabling them to work better and faster.
Unfortunately, SharePoint doesn’t have any built-in HTML5 goodness, but what it does have is great flexibility. In this article, I’m going to demonstrate how easy it is to add HTML5 drag-and-drop support to SharePoint—and how smooth it can make the standard interface, as shown in Figure 1.
Figure 1 Drag and Drop in SharePoint
To implement this, I’ll use one of the essential SharePoint building blocks, which is also one of my favorite SharePoint tools—the XsltListViewWebPart and its XSL transformations (see the MSDN Library page atbit.ly/wZVSFx for details).
Why Not a Custom Web Part?
As always, when it comes to implementation, SharePoint offers a wide range of possibilities, and it’s exceedingly important to pick the one that will serve you best.
For the HTML5 drag-and-drop challenge, considering that drag and drop is mainly for managing data, many SharePoint developers would probably prefer to build a custom Web Part, which in this case acts just like an ordinary ASP.NET control: the data is stored in a standard SharePoint list, retrieved through the object model or SPDataSource control, and rendered with the help of ASCX markup and ASP.NET controls.
Simple, clear, plain ... but the best choice?
Two years ago I thought so. Today, I’d prefer to customize XsltListViewWebPart using its XSL transformations. Why did I change my mind?
Starting with SharePoint 2010, almost all kinds of list views (with the single exception of Calendars) are displayed through this very Web Part. Just imagine: all these data types, all these different views and styles and list types, all this great variety of data is rendered using XsltListViewWebPart and its XSL transformations. It’s a flexible and powerful tool.
If you decide to jump in and build your own custom Web Part to render some HTML5 markup for displaying list data, you’ll lose all of the built-in features. And based on my experience, that’s a huge loss. By the way, I haven’t seen a single custom Web Part yet that didn’t, at the very least, end up implementing half of the out-of-the-box XsltListViewWebPart features.
So, my plan is to reuse existing functionality rather than create a similar custom Web Part that would probably be much worse in terms of flexibility and power.
In fact, XsltListViewWebPart includes a bunch of useful features. It’s integrated into SharePoint Designer, it supports all possible Web Part connections and it displays all the SharePoint data types properly. It supports grouping, subtotals, paging, item context menus, inline editing, item selections, presence indicators and more. It has a contextual Ribbon interface, provides a UI for sorting and filtering, offers some basic view styles and more again. In sum, XsltListViewWebPart has a great many useful features that would be very hard to re-implement using the custom Web Part approach.
XsltListViewWebPart
XsltListViewWebPart provides many integration points for developers: a programmatic interface, a CAML interface and, of course, XSL transformations in conjunction with parameter bindings. And don’t forget, all these objects and properties also have their representations in the Client Object Model, so you can access your XsltListViewWebPart even from JavaScript or Silverlight.
So, XsltListViewWebPart is really a powerful tool. True, all this SharePoint-specific XML (or XSLT) looks a bit scary at initial glance, but there are some “life hacks” I’m going to show you that will help you puzzle it out.
The Scenario
Before I dive into the implementation details, let me describe the overall scenario.
What I’m going to do is inject HTML5 drag and drop into SharePoint list views to enable users to drag cells from one list to another list. My example will use a Tasks list and an Executors list, so the Project Manager can easily assign and reassign tasks, dragging executors to corresponding Tasks list cells.
As you might know, HTML5 introduces several new attributes for drag and drop, the most important of which is the “draggable” attribute. There are also a number of events for handling various stages of the drag-and-drop process. Handler functions for these events can be attached using corresponding attributes, such as “ondragstart,” “ondragend” and so forth. (For details, read the World Wide Web Consortium [W3C] HTML5 specification draft, Chapter 7.6, at bit.ly/lNL0FO.)
For my example, this means I just need to use XSLT to add some basic attributes to certain list view cells, and probably some additional custom attributes to attach the data values (that will be conveyed by dragging). Eventually, I’ll need to provide the corresponding JavaScript code for the handler functions.
First Steps
I need two lists. I can create a Tasks list from the standard Tasks template, or I can just create a custom list and add some columns, including an obligatory “Assigned To” site column. I create a second list, Executors, as a custom list, adding “Executor” as a column of type “Person or group,” making it required, indexed and unique.
The Executors list should display only user names; thus it doesn’t actually need the standard “Title” column. To hide this column, I go to list settings, enable management of content types, then go to the “Item” content type, click on the “Title” column and make the column hidden, as shown in Figure 2.
Figure 2 Making the Title Column Hidden in SharePoint List Settings
I filled these lists with sample data and then created a Web Part page for my dashboard, where I added these lists side-by-side (Tasks on the left and Executors on the right), as shown in Figure 3.
Figure 3 Adding the Lists to the SharePoint Dashboard
OK, now I have the lists and I have the data. Now it’s time for the actual implementation of the drag-and-drop functionality.
SharePoint Designer
Microsoft SharePoint Designer is a completely free tool for rapid development of SharePoint applications. SharePoint 2010 is greatly improved as compared with SharePoint 2007, and now it’s exceedingly useful, even for developers. The idea is that you can use the SharePoint Designer GUI to generate some really complex XSLT code and then just copy and paste the generated code into your Visual Studio project instead of writing the typo-prone and not always well-documented XML/XSLT by hand. I often use this trick in real-world projects and, I promise you, it saves plenty of time.
I open SharePoint Designer and navigate to the dashboard page I created earlier. I select a cell in the Assigned To column (right-click and choose Select | Cell). Now, the magic: in the status bar, I see the path to the XSL template (and to the corresponding HTML tag within this template) that’s responsible for displaying this particular cell (see Figure 4).
Figure 4 The Path to the Current XSL Template in SharePoint Designer
This information can be very useful for determining which XSL template to override in order to change the cell markup. You can find the original code for the templates in the 14/TEMPLATE/LAYOUTS/XSL folder, and use it in your own XSLT files or in the <Xsl> tag of the XsltListViewWebPart.
But I don’t need to deal with these huge and complicated XSLT files to achieve my goal. Instead, I can use the SharePoint Designer conditional formatting feature, which is designed to highlight certain rows or cells with special formatting, based on particular conditions. You don’t need any special skills to use this feature; the GUI makes it easy. But behind the scenes, it’s all implemented with XSLT. Thus, SharePoint Designer includes a kind of ready-to-use graphical XSLT generator, and I’m going to use it now for my own needs.
I select a cell, click the Conditional Formatting button on the Ribbon and then select Format Column, as shown in Figure 5.
Figure 5 Setting Conditional Formatting in SharePoint Designer
Next, I create an unlikely condition, ID equal to zero, as shown in Figure 6.
Figure 6 The Conditional Formatting Dialog in SharePoint Designer
Then I click the Set Style button and select some random style (such as “text-decoration: underline”). I press OK and switch to the Code View tab, where I locate the generated code; it is, of course, inside the <Xsl> tag of the XsltListViewWebPart control.
XSL Transformations
Now I’m ready to modify the markup of “Assigned To” cells. The “Assigned To” column is the “data acceptor” where I’ll drag executors, so I need to provide the “ondragover,” “ondragenter,” “ondragleave” and “ondrop” attributes, which will point to the corresponding JavaScript event handler functions.
The code generated by SharePoint Designer in the previous paragraph contains the XSL template with the following signature:
<xsl:template name="FieldRef_printTableCell_EcbAllowed.AssignedTo"
match="FieldRef[@Name='AssignedTo']" mode="printTableCellEcbAllowed"
ddwrt:dvt_mode="body" ddwrt:ghost="" xmlns:ddwrt2="urn:frontpage:internal">
As you might know, XSL templates can call each other, either by name or by condition. The first type of call is performed using the “xsl:call-template” element, and it’s very similar to a function call—such as what you’d use in C#, for example.
The second option is preferable and much more flexible: by using the “xsl:apply-templates” element, you can specify the mode and the parameter (which is selected using XPath so it can actually contain many elements), without specifying any particular template name. For each parameter element, the corresponding template will be matched using the “match” attribute. You can think of this approach as something similar to overloads in C#.
As you can see in the preceding code, this template will match “FieldRef” elements, where the Name attribute is equal to “AssignedTo.” Also, the “mode” attribute of the corresponding xsl:apply-template call must be equal to “printTableCellEcbAllowed.” So this template is essentially an overload for the standard function that displays fields’ values. And this overload will match only the “Assigned To” field values.
Now let’s take a look at what’s inside this template, as shown in Figure 7 (some code was removed for clarity).
Figure 7 Inside the XSL Template
<xsl:template match="FieldRef[@Name='AssignedTo']" mode="printTableCellEcbAllowed" ...>
<xsl:param name="thisNode" select="."/>
<xsl:param name="class" />
<td>
<xsl:attribute name="style">
<!-- ... -->
</xsl:attribute>
<xsl:if test="@ClassInfo='Menu' or @ListItemMenu='TRUE'">
<xsl:attribute name="height">100%</xsl:attribute>
<xsl:attribute name="onmouseover">OnChildItem(this)</xsl:attribute>
</xsl:if>
<xsl:attribute name="class">
<!-- ... -->
</xsl:attribute>
<xsl:apply-templates select="." mode="PrintFieldWithECB">
<xsl:with-param name="thisNode" select="$thisNode"/>
</xsl:apply-templates>
</td>
</xsl:template>
As you see, the template contains two xsl:param elements, one <td> element, several xsl:attribute elements and an xsl:apply-templates element, which will cause some lower-level templates to be applied.
To achieve my drag-and-drop goal, I just add the drag-and-drop attributes to the <td> element, like so:
- <td ondragover="return UserDragOver(event, this)" ondragenter=
- "return UserDragOver(event, this)" ondragleave=
- "UserDragLeave(event, this)" ondrop="UserDrop(event, this)">
Pretty simple, isn’t it?
Alternatively, if you have jQuery deployed in your SharePoint environment, you might consider attaching JavaScript event handlers using the jQuery .on method.
The markup for the Assigned To column is ready (I’ll write the handlers a bit later). Now it’s time to customize the Executors list.
I switch back to the Design View tab, select a cell in the Executors column and repeat the conditional formatting trick for generating the XSLT code. Then I add the onstartdrag attribute to ensure that the drag and drop can properly start (I don’t need the draggable attribute here, because “Person or Group” field values are rendered as links and, according to the specification, links have the draggable attribute set to “true” by default):
- <td ondragstart="UserDragStart(event, this)">
Cool. But how will I track the data? How do I determine which executor is being dragged? Obviously, I need his login name or, better, his ID. Parsing the ID from inside the TD element is unreasonably complicated, in my opinion.
The point here is that in XSLT, for any field of type Person or Group, the user ID is available and can be easily retrieved with a simple XPath query.
In this query, I need to point to the current element’s values. The current element is usually referenced as the $thisNode parameter in all standard XsltListViewWebPart templates. To retrieve the user’s ID, you point to the attribute of the $thisNode parameter, with name equal to the name of the Person or Group column, with “.id” concatenated to its end.
So here’s my query:
- <td ondragstart="UserDragStart(event, {$thisNode/Executor.id}, this)">
The curly brackets are used for including the XPath expression right in the attribute value.
The markup is ready and can actually be used right away, but it would probably be a good idea to work with this code a little more to make it more reusable.
Making Templates Reusable
You might have noticed that the templates are tightly bound to specific column names and that these templates are intended only for particular lists. But it’s actually very simple to modify these templates so you can reuse them for other lists with other column names.
To start, if you examine the template signature I presented earlier, you’ll see the following attribute:
match="FieldRef[@Name='AssignedTo']"
Obviously, this binds the template to the Assigned To column. To make this template a bit broader-based, you can replace the name binding with a type binding, so that any Person or Group column will match. So be it! Here’s the code:
match="FieldRef[@Type='User']"
The same modification should be applied to the second template, where the FieldRef element is matched to the “Executor” field internal name.
Now, because I can have any column of type Person or Group and any list, I need to pass some additional information to my JavaScript handlers. When drag and drop is performed, I need to update the value of the Assigned To column in the Tasks list, so I need to know the name of the column and GUID of the list.
As I mentioned previously, SharePoint XSLT has some standard parameters that are available globally. One such parameter is $List, which stores the current list’s GUID. And the internal name of the field can be easily retrieved from the matched FieldRef element.
So, I’m going to pass the list GUID and the column internal name to the UserDrop handler, as follows (I’m omitting the “ondragenter,” “ondragover” and “ondragleave” attributes for clarity):
- <td ... ondrop="UserDrop(event, this, '{$List}', '{./@Name}'">
The “.” points to the current FieldRef element (which was matched previously by the template).
Next, I need to get rid of the “Executor” string in the id parameter in the Executors list XSLT using the following code:
- <td ondragstart
- ="UserDragStart(event, {$thisNode/@*[name()=
- concat(current()/@Name, '.id')]}, this)"
>
The templates are now ready and reusable, and now I’m going to write the corresponding JavaScript code to implement the handlers.
Writing JavaScript Handlers
Although there are four different handlers to write, most of them are primitive and they’re all rather obvious.
Usually I’d recommend placing this code in a separate JavaScript file. And it generally would be a good idea to use Visual Studio to create it, as you’d get the benefit of IntelliSense there. In some circumstances, however, it’s reasonable to put this code inside the XSLT, overriding the root template (match=“/”) for this purpose. This lets you use some XSLT variables and parameters inside your JavaScript code and it also means you don’t have to be concerned about deploying JavaScript files.
So let’s look at the code for the handlers—DragStart, DragEnter, DragOver, DragLeave and Drop.
In the UserDragStart handler, you need to initialize the data transfer. This means you need to store dragged data in a special HTML5 DataTransfer object, like this:
- function UserDragStart(e, id, element) {
- e.dataTransfer.effectAllowed = 'copy';
- e.dataTransfer.setData('Text', id + '|' + element.innerHTML);
- }
Note that the ID of the user is not the only part of the data that’s transferred. I also added the inner HTML of the <td> element, to avoid having to refresh the page after dropping the data (see the UserDrop handler code in Figure 8 for the details).
Figure 8 The Drop Event Handler
- function UserDrop(e, toElement, listGuid, columnName) {
- // Terminate the event processing
- if (e.stopPropagation)
- e.stopPropagation();
- // Prevent default browser action
- if (e.preventDefault)
- e.preventDefault();
- // Remove styles from the placeholder
- toElement.style.backgroundColor = '';
- //toElement.className = '';
- // iid attribute is attached to tr element by SharePoint
- // and contains ID of the current element
- var elementId = toElement.parentNode.getAttribute('iid').split(',')[1];
- // Transferred data
- var data = e.dataTransfer.getData('Text');
- var userId = data.split('|')[0];
- var userLinkHtml = data.split('|')[1];
- // Setting value of the field using SharePoint
- // EcmaScript Client Object Model
- var ctx = new SP.ClientContext.get_current();
- var list = ctx.get_web().get_lists().getById(listGuid);
- var item = list.getItemById(elementId);
- item.set_item(columnName, userId);
- item.update();
- // Asynchronous call
- ctx.executeQueryAsync(
- function () { toElement.innerHTML = userLinkHtml; },
- function (sender, args) { alert('Drag-and-drop failed.
- Message: ' + args.get_message()) }
- );
- return false;
- }
For the DragEnter and DragOver events in this case, the handlers are identical so I’m using a single function for them. You should indicate in these events that the user can drop his data here. According to the specification, for this purpose you should call the event.preventDefault method (if available), and then return false.
The DragEnter/DragOver handler is the perfect place to apply custom styles to the drag-and-drop placeholder to notify the user that he actually can drop his dragged data. To simplify the example, I’ll use inline CSS styles, but in a real-world project I’d recommend using CSS classes (see the commented lines shown in Figure 9).
Figure 9 The DragOver and DragLeave Event Handlers
- function UserDragOver(e, toElement) {
- // Highlight the placeholder
- toElement.style.backgroundColor = '#efe';
- // toElement.className = 'userDragOver';
- // Denote the ability to drop
- if (e.preventDefault)
- e.preventDefault();
- e.dataTransfer.dropEffect = 'copy';
- return false;
- }
- function UserDragLeave(e, toElement) {
- // Remove styles
- toElement.style.backgroundColor = '';
- // toElement.className = '';
Obviously, in DragLeave I need to remove the previously applied styles, as shown in Figure 9.
During the Drop event in Figure 8, two important actions need to be performed:
- Apply the changes. Using the SharePoint EcmaScript Client Object Model, set the field value to the transferred user ID.
- Replace the inner HTML code in the current cell (TD) with code from the transferred one to make the changes appear without refreshing the page.
Now all handlers are implemented and you can deploy the JavaScript. Actually, you can do this in many different ways: customize the master page, use delegate control, create a custom action with Location set to “ScriptLink” or, as I mentioned earlier, include JavaScript right in the XSLT.
The simplest way here is to customize the master page file using SharePoint Designer, as it doesn’t require any special skills. But because you’re a developer, chances are you’d prefer to gather together all these JavaScript and XSLT customizations and create a deployable solution. OK, let’s do it!
Building a Deployable Solution
To create a ready-to-use solution you can send to your customers, you need to perform some very simple steps:
- Open Visual Studio and create an Empty SharePoint Project. Then select “deploy as a farm solution” in the project settings dialog.
- Add a SharePoint “Layouts” Mapped Folder to your project, and add three new files to it: UserDragHandlers.js, UserDragProvider.xsl and UserDragConsumer.xsl.
- Paste the previously created JavaScript code and the XSLT code (generated in SharePoint Designer) into corresponding files.
- Add an Empty Element project item, open Elements.xml and paste the following code there:
<CustomAction ScriptSrc="/_layouts/SPDragAndDrop/UserDragHandlers.js" Location="ScriptLink" Sequence="10" />
This will deploy the link to your JavaScript file to all site pages. - Finally, add an Event Receiver to Feature1 (which was created automatically when you added the Empty Element project item), uncomment the FeatureActivated method and paste the following code in it:
- var web = properties.Feature.Parent as SPWeb;
- SPList list;
- SPView view;
- list = web.Lists["Tasks"];
- view = list.DefaultView;
- view.XslLink = "../SPDragAndDrop/UserDragConsumer.xsl";
- view.Update();
- list = web.Lists["Executors"];
- view = list.DefaultView;
- view.XslLink = "../SPDragAndDrop/UserDragProvider.xsl";
- view.Update();
And, yes, that’s all—you’re done! Pretty simple, isn’t it? Now if you build and deploy the solution with the Tasks and Executors lists created previously, and then create a dashboard containing default views of each of them, your dashboard will sport a handy drag-and-drop feature.
Browser Support
By now, just about all of the major browsers except Opera support HTML5 drag and drop. I’ve tested Internet Explorer 9.0.8112.16421, Chrome 16.0.912.75 and Firefox 3.6.12, and they’re all fully compatible with the described solution. I expect that some older versions of these browsers will work too. Actually, Safari should also work, but as of this writing, it has some strange bugs in its HTML5 drag-and-drop implementation that prevent the solution from working as expected.
No comments:
Post a Comment