Showing posts with label SharePoint. Show all posts
Showing posts with label SharePoint. Show all posts

Friday, 1 August 2014

Difficulties overriding XLV web parts with external stylesheets in SharePoint libraries

Overriding an XSLT List Web Part runs into some limitations when using an external stylesheet with the XslLink property. The problem is that the ECB menu (which comes up when you click the arrow next to a list item name or title) is broken. Also, many of the item related ribbon buttons under the Documents tab are greyed out. The combination of those two problems makes for a very crippled web part which is good for not much more than displaying the list items.
Reproducing the problem is more specific than I've suggested. Only external stylesheets located in the SharePoint content database have these problems. In other words, stylesheets located in one of your document libraries (such as the root site's Style Library) break the ECB and the ribbon. Stylesheets located on the file system in the _layouts folder do not. This is all explained in some detail by Glyn Clough in his SharePoint blog - XLV Bug: XSL Link Property and the ECB Menu.
I've done a bit of my own debugging and found that the XslLink problem affects the ECB menu and the ribbon whether you use a fully qualified absolute URL, a server relative URL, or a page relative URL. The ECB menus and ribbon controls are rendered as a result of respective AJAX calls to obtain HTML to add to the DOM node tree. This includes script source​, which is executed by the browser. It's a rather clunky and insecure way to do it, if you ask me, and it may even be vulnerable to cross-site scripting exploits. Nevermind that. Long story short, when the XSLT stylesheet is in the web part definition in the page itself, no problem. When the stylesheet is external an in the content database, the SharePoint application page /_layouts/inplview.aspx is unable to create the output required.
For the ECB menu, the URL would look something like this:
http://[my host]/[path to my site]/_layouts/inplview.aspx?Cmd=Ctx&List={GUID of list}&View={GUID of view}&ViewCount=1&IsXslView=TRUE&Field=LinkFilename&ID=5&ListViewPageUrl=[URL of my page]
Plugging that URL into a browser results in the following response:
<div class="ms-vb">Unable to display this Web Part. To troubleshoot the problem, open this Web page in a Microsoft SharePoint Foundation-compatible HTML editor such as Microsoft SharePoint Designer. If the problem persists, contact your Web server administrator.</div>
<br/><br/>Correlation ID:[correlation ID GUID]<br/>
The javascript doesn't find what it's looking for in the HTML source of that response, and the result is a canned alert box displaying the unhelpful message "This item is no longer available.  It may have been deleted by another user.  Click 'OK' to refresh the page."
It turns out there are a couple decent workarounds to this problem with the ECB menu apart from keeping the XSLT in the page. The simplest workaround is to edit the web part in the browser and tick "Enable Asynchronous Update" under AJAX Options in the web part properites. This causes the ECB menu to be loaded in a different way that does not rely on the bad call to inplview.aspx. What other side effects it may have, desirable or otherwise, I'm not sure.
Another good workaround that avoids editing the web part properties is this one, discovered by Henk Haarsma: XSLT in sandbox solution. This technique uses javascript in the XSLT code to change the URL in the AJAX call so that the isXslView parameter is removed. That parameter does not seem to be needed in this call. When the call is working because the conditions for the bug are not present, the removal of the query string parameter makes no difference to the output.
Neither of these workarounds solve the ribbon problem. Encouraged by the success of the "Enable Asynchronous Update" solution, I tried all sorts of other settings in the web part properties. Nothing made a difference. The javascript solution prompted my to try the same thing in another part of the stylesheet that controls the URL to the AJAX call for the ribbon settings. That URL looks something like this:
http://[my host]/[path to my site]/_layouts/inplview.aspx?List={GUID of list}&View={GUID of view}&ViewCount=1688&ListViewPageUrl=[URL of my page]&IsXslView=TRUE&Cmd=EcbView
It shows the same output as the broken ECB call and removing the isXslView query string parameter does seem to fix it; however, when the call is working properly the output with the isXslView parameter is very different. So that's not a solution for the ribbon.
There is, however, hope. The two calls seem to fail for different reasons, or in different ways. There is another way to pull in an external stylesheet. You can use a very simple inline stylesheet in the web part's Xsl property that does nothing more than an xsl:include of the external file. It looks something like this:
<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl
ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" 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:o="urn:schemas-microsoft-com:office:office"
ddwrt:ghost="show_all">
<xsl:include href="[path to external XSL file]"/>
</xsl:stylesheet>
An xsl:import would give identical results in this case. Using this method had no effect on the rendering of the ECB menu; however, with an absolute URL supplied the ribbon does work! So you can use one of the two methods I described earlier to enable the ECB menu combined with this method for solving the ribbon problem for a fully functioning web part. It is unfortunate to have to use an absolute URL, but at least that shows it's possible. Debugging with .Net Reflector shows that strange things are happening when SharePoint is trying to resolve the URL to the stylesheet in the ribbon AJAX call, and it fails in different ways depending on how the external file is referenced. I'm hoping that with a bit more digging I'll be able to find a workaround for page relative and server relative URLs as well.

Tuesday, 13 September 2011

SharePoint 2010 UI: custom action to launch "Move To" for a list item

Mood = :proud:

I've worked out how to create a custom menu action in a SharePoint 2010 list view that allows a user to move a single file (with meta-data intact) to another location similar to the functionality available in Site Content and Structure. In fact, I have made use of the Site Content and Structure page directly within a modal dialog. You can use SharePoint Designer to create a custom list item menu action. I won't go into detail here on how to do that as there is plenty of information available on the web. In the "Create Custom Action" dialog, tick the radio button next to "Navigate to URL:" and paste in the following javascript URL all on a single line (you will need to change the webId value to your own):

javascript:var listId='{ListId}'.toLowerCase().replace(/[{}]/g, ""); var webId='4836a39d-e154-4822-8826-8bcd5029cad2'; var dlg=SP.UI.ModalDialog.showModalDialog({ url: "{SiteUrl}/_Layouts/sitemanager.aspx?FilterOnly=1&SmtContext=SPList:" + listId + "?SPWeb:" + webId + ":&SmtContextExpanded=True&Filter=1&pgsz=100&vrmode=False", dialogReturnValueCallback: function(res, val) {location.reload(true);}}); var idString="SPListItem:{ItemId}?SPList:" + listId + "?SPWeb:" + webId; var item; var func; var doc; var win; setTimeout(func = function() {for (var i=0; i < window.top.frames.length; i++) {if (window.top.frames[i].location.href.indexOf("sitemanager.aspx") != -1) {win = window.top.frames[i]; doc = win.document;}}; if (doc == null || doc.forms.length == 0) {return setTimeout(func, 5000);}; for (var i=0; i < doc.forms[0].elements.length; i++) {var elem=doc.forms[0].elements[i]; if (elem.type == 'checkbox' && elem.id == idString) {item = elem; break;}}; if (item == null) {return setTimeout(func, 500);}; win.SmtSingleObjectMove(idString+":", false); item.checked=true; win.SmtGridSelectItem(item);}, 100); return false;


I had to use some setTimeout calls in order to ensure that the objects I was referring to were present. There is a particularly long timeout of 5 seconds that allows the dialog to hopefully complete rendering before it attempts to launch the popup with the tree view that actually selects the destination. All efforts to encapsulate this in the dialog's onload function met with failure. Perhaps one day I'll work that one out. Closing the dialog will refresh the view. This should work for any list or library in your SharePoint web application. Unfortunately there is no URL token corresponsing to the guid of the web application, so this will need to be provided as a hard-code value as I have done. I hope this is helpful to someone, as it took quite a lot of trial and error.

Wednesday, 7 September 2011

SharePoint 2010 - weirdness with custom list item edit form

I don't like SharePoint very much, but I'm forced to work with it in my software development role. A lot of what you might think of as simple functionality that could be expected out-of-the-box in a commercial content management system actually requires custom coded solution. Okay, I can do that. The trouble is though that the IT department at my company are quite strict about the provisioning of custom software. Given how buggy SharePoint Services is, I can't say I'm terribly surprised. The upshot is that any custom coded solution is discouraged in favour of some third party alternative if available. And either way, it needs to be justified. I tend not to bother with custom solutions. I often encounter weird problems with the out-of-the-box functionality though. Here's one.

I have a requirement to allow designated people to move a document from a staging document library to a permanent location once the document has been approved. After lots of mucking around I've found that the simplest way to do this is to create a custom edit form that allows input of only one item property. I've decided to use the Source URL property, since this is not actually useful in the context of the library. The plan is that there will be a workflow that kicks off whenever the document properties are changed and if there is a value for Source URL, the workflow will move the document to within that path (if legal) and then terminate. As an aside, I really wish the standard list views gave access to the file move functionality. You can't move a file from with a library, you can only send it to another location as a copy. But that's only one from an ever growing list of gripes, and not a very important one.

So anyway, I've found that this can work. However, when I (or the user) submits the custom edit form (which by the way cannot open as a dialog like the built in forms do), the result is that the document is output by the browser as an attachment. This causes a download dialog to open. I really don't want that to happen. It is confusing, to say the least. If I put the IsDialog=1 query string variable on the URL, this does not happen; however, I can't control that from actions and it shouldn't open as a dialog anyway. Also, the form does not close itself and redirect to the list as I'd expect it to. This seems to be a problem only with the custom edit forms. The default EditForm.aspx does not exhibit this strange behaviour, whether or not it is in dialog mode. I can't find anything about it on teh interwebs, so I thought I'd be the first to report it. If anything comes back, I'll be very surprised though.

Flayman on LiveJournal (old)