SharePoint Edit Links (Drag and Drop Navigation) option missing

If you’ve been using SharePoint 2013 you may have noticed the new ability to edit global navigation and quick launch navigation on the fly and to use drag and drop capabilities to create new links. This ability is provided via the ‘EDIT LINKS’ option in both the global navigation and quick launch navigation controls:

 image

However, you may notice that this ability is not available on all webs. This is because the SharePoint navigation provider limits this function to webs that have been created from a specific list of web templates:

  • Team Site – STS#0
  • My Site Host – SPSSITEHOST#0
  • SharePoint Portal Server Personal Space – SPSPERS#0
  • Storage And Social SharePoint Portal Server Personal Space – SPSPERS#2
  • Storage Only SharePoint Portal Server Personal Space – SPSPERS#3
  • Social Only SharePoint Portal Server Personal Space – SPSPERS#4
  • Empty SharePoint Portal Server Personal Space – SPSPERS#5
  • Project Site – PROJECTSITE#0
    HTH.

Excel Service REST API Ribbon Button

On of my favourite areas of SharePoint BI to demonstrate is the Excel Services REST API. It’s a great way to reuse content and logic embedded inside existing spread sheets but needs to be accessed via a ‘special’ URL. The Office team has got a great blog article on how to construct the required URL to access your Excel assets via the REST API.

However, constructing these URLs requires a little cutting and pasting so I’ve created a SharePoint solution that constructs the required URL for you. When a single XLSX based spread sheet is selected in a document library, a ‘Excel REST’ ribbon button is enabled:

image

Clicking on the ribbon button displays the URL required to access the select spread sheet via the REST API:

image

Here’s how the solution is structured:

image

Firstly there is a Elements file that is used to define a ScriptLink and a ribbon button. The ScriptLink is used to inject the JavaScript library that contains the logic for the ribbon button into the page:

image

The ribbon button is defined by a second CustomAction in the Elements file and most importantly defines the JavaScript functions to be called to determine if the ribbon button should be enabled (EnabledScript) and what happens when the button is clicked (CommandAction):

image

The above two functions are contained in the excelRest.js JavaScript library which is deployed to the SharePoint root via a mapped folder:

image

This JavaScript library contains the functions required to determine if the button should be enabled (based on selected item type) and then builds and displays the REST API URL for the selected item when the ribbon button is clicked:

var _site;
var _web;
var _file;
var _selectedItemType;
var _selectedItemId;


// this function is called when the ribbon button is pressed
function invokeExcelREST() {

    // construct the rest URL for our dialog
    var webUrl = _web.get_serverRelativeUrl();
    if (webUrl == '/') {
        webUrl = '';
    }
    var restUrl = _site.get_url() + webUrl + '/_vti_bin/ExcelRest.aspx' + _file.get_serverRelativeUrl().replace(webUrl, '') + '/Model';
    
    // build the html content for our dialog
    var htmlContent = document.createElement('div');
    htmlContent.setAttribute('style', 'padding-top:10px; padding-left: 10px');
    var htmlIcon = document.createElement('img');
    htmlIcon.setAttribute('src', '/_layouts/images/icxlsx.png');
    htmlContent.appendChild(htmlIcon);
    var htmlSpan = document.createElement('span');
    htmlSpan.setAttribute('style', 'padding-left: 10px;');
    var htmlLink = document.createElement('a');
    htmlLink.setAttribute('href', restUrl);
    htmlLink.innerHTML = restUrl;
    htmlSpan.appendChild(htmlLink);
    htmlContent.appendChild(htmlSpan);
            
    // build the options for our dialog
    var options = {
        html: htmlContent,
        autoSize: true,
        title: 'Excel REST URL for ' + _file.get_name(),
        allowMaximize: false,
        showClose: true
    }

    // call our dialog
    SP.UI.ModalDialog.showModalDialog(options);
}


// this function is used to determine if the ribbon button 
// should be active or not based on the selected document type
function enableExcelREST() {
    
    // get the collection of selected items
    var items = SP.ListOperation.Selection.getSelectedItems();
    
    // check that only one item is selected
    if (items.length == 1) {

        // get the first (only) selected item
        var item = items[0];

        // get the listid of selected item
        var listID = SP.ListOperation.Selection.getSelectedList();

        // check to determine if the current execution of this function
        // is due to a RefreshCommandUI call
        if (_selectedItemId == null && _selectedItemType == null) {
            // this is the first execution of this function

            // store the selected item id
            _selectedItemId = item['id'];
            
            // prepare a CSOM query to get the selected item
            _selectedItemType = null;
            var listGuid = SP.ListOperation.Selection.getSelectedList();
            getStatus(_selectedItemId, listGuid);
        }
        else {
            // this path is called post a RefreshCommandUI that
            // is initiated from a successful CSOM query
            if (_selectedItemType == 'xlsx') {
                // we have an xlsx file type so enable the ribbon button
                _selectedItemId = null;
                _selectedItemType = null;
                return true;
            }
            else {
                // we do not have an xlsx file type so disable the ribbon button
                _selectedItemId = null;
                _selectedItemType = null;
                return false;
            }
        }
    }
    else {
        // more than one item was selected
        return false; // disable the ribbon button
    }
}

// this function gets called when the CSOM query has completed
function onStatusQuerySucceeded(sender, args) {

    // remember the selected item file type and title
    _selectedItemType = IssueItem.get_item('ows_File_x0020_Type');

    // this causes the enabledScript function to be re-executed
    // but this time we've already set the _selectedItemId so
    // a different logic path will be followed in enableExcelREST
    RefreshCommandUI();
}

// this function is called is the CSOM query bombs
function onQueryFailed(sender, args) {
    alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}

// this function builds and execute a CSOM query to fetch the selected item
function getStatus(ItemId, listGuid) {
    var clientContext = new SP.ClientContext();
    _site = clientContext.get_site();
    clientContext.load(_site);
     _web = clientContext.get_web();
    clientContext.load(_web);
    var lists = _web.get_lists();
    var list = lists.getById(listGuid);
    this.IssueItem = list.getItemById(ItemId);
    _file = IssueItem.get_file();

    // specify the columns we wish to return
    clientContext.load(IssueItem, 'Title', 'ows_File_x0020_Type');
    clientContext.load(_file);
    // execute the query
    clientContext.executeQueryAsync(onStatusQuerySucceeded, onQueryFailed);
}

If you want to download the completed solution, it can be found here: http://sdrv.ms/Juvbc1

I hope this helps…

Track SharePoint Content Database Growth via Central Admin

Following on from a recent post I made about a SharePoint health analyzer rule that can be used to automatically expand a SharePoint content database outside of normal working hours, I wanted to create a solution for monitoring content databases growth over time via central admin. Here’s what I came up with:

image

The solution consists of four parts, the first part is the Review Databases Sizes page shown above. The page is accessed from a custom action under Application Management > Databases:

image

The Review Databases Sizes page lists each content database present in the farm, plus a spark line that shows the database data file size and log file size over time. Clicking on the database name or either of the spark lines shows the second part of the solution, the Database Size Details application page. This page will be displayed to you inside an SP.UI.ModalDialog:

image

imageThe chart shown in the modal dialog (and the spark lines) are created via the jqPlot jQuery extension and allow for some nifty features such as data point highlighting, animated rendering and zooming. Note: You may need to check the jqPlot browser requirements to ensure this will work in your environment.

To zoom into an area on the chart simply click and then drag a rectangle that contains the data to be explored:

The chart will be re-rendered to display just the data points contained in the area you selected.

After you’ve zoomed in, you can examine individual values by hovering your mouse over a data point or you can zoom back out to the full chart by double clicking anywhere on the chart.

image

The third part of the solution is the deployment of the jqPlot JavaScript libraries themselves. The required libraries are deployed by a SharePoint feature and use ScriptLinks to add themselves to the master page of central admin without updating the master page itself. I’ve used this simple and powerful method to deploy jQuery libraries before and more details about can be found here: Use jQuery in SharePoint without updating your MasterPage

The fourth and final part of the solution is a custom timer job that is set to run once a day sometime between midnight and 1am. Its called ‘SPHealth Database Size Collection’:

image

The timer job finds each content database in the farm and demines the size of the database data file and log files for each. The sizes are then stored in the property bag for each content database.

That’s it – two application pages, a timer job, and a couple of module files. Smile

I’ve published the source code to the solution at http://sphealthdbsize.codeplex.com if you want to have a poke around and try it out for yourself. Caveat: Before you deploy this in a production farm, just like any other third party solution, I would recommend you review and understand what the code is doing before you use it. Also note the following, it may take a couple of days before you see any charts as the timer job will need to have run twice to have collected enough data points to plot!

Enjoy…

Programmatically enable Ajax Auto Refresh on a SharePoint XsltListViewWebPart

On of the capabilities of the XsltListViewWebPart used extensively by SharePoint 2010 is the automatic refreshing of list view data via Ajax. Used judiciously, this can be very useful for creating dynamic views of data, pseudo-dashboards or in my case, providing feedback of timer job processing via the UI.

To enable automatic refresh via the UI is simple, edit the web part properties, and switch on the ‘Enable Asynchronous Automatic Refresh’, set the refreshing interval (which must be no more frequent than 15 seconds) and save the web part.

image 

To achieve the same results via code is just as simple:

// update the XsltListViewWebPart to refresh automatically via Ajax
// get the webpart page and webpart manager
SPFile file = web.GetFile("Lists/<your list name>/AllItems.aspx");
SPLimitedWebPartManager wpManager = 
    file.GetLimitedWebPartManager(PersonalizationScope.Shared);

// get the XsltListViewWebPart...
// assumes the XsltListViewWebPart is the first webpart on the page
XsltListViewWebPart lvwp = (XsltListViewWebPart)wpManager.WebParts[0];
                
// set the webpart to autorefresh and set the interval
lvwp.AutoRefresh = true;
lvwp.AutoRefreshInterval = 15;

// save the changes
wpManager.SaveChanges(lvwp);

Enjoy…

Use jQuery in SharePoint without updating your MasterPage

Frequently I need to use jQuery in SharePoint but don’t always want to have to edit and publish a revised MasterPage to include the necessary script links that will allow me to access the jQuery libraries. This is where the ScriptLink class comes to our rescue. The ScriptLink class allows us to ‘inject’ resources into our pages without having to update the source HTML. What’s even better is we can deploy ScriptLinks via custom actions so we can use the SharePoint feature framework to affect our changes.

Here’s how I go about creating a feature that when deployed and activated will add the jQuery libraries to my site without changing any pages or MasterPages:

First, I create an new Visual Studio 2010 ‘Empty SharePoint Project’ and add to the project a new ‘Layouts’ mapped folder, to this directory I add all the jQuery (and other) resources I want to use in my site:

image

Note: I’m creating my own jQuery folder underneath the existing _layouts/inc’ folder. This is because the ‘inc’ folder is one of the few directories underneath the _layouts folder that is automatically configured for caching and anonymous access. You don’t need to create your folder underneath the existing ‘inc’ directory but you should at least be creating your own directory below the _layouts folder.

Next, I create an elements file that includes all the resources I wish to include in my pages. Each resource is added as a CustomAction with the Location attribute set to ‘ScriptLink’ and the ScriptSrc attribute set to the relative path of the resource:

image

Finally I include the elements file in a feature:

image

Now once this is deployed to my site and activated, the following script links are automatically included in the rendered HTML of the site:

image 

Now I can use jQuery on my site and I’ve had to alter or switch MasterPage – yay! Obviously deactivating the feature then removes these script links from the rendered HTML.

P.S. One other thing you can do to streamline the solution that contains this feature is to switch off the inclusion of the default assembly that Visual Studio will include with the project. As this solution contains no managed code whatsoever the assembly that gets created for us by default by Visual Studio is totally redundant. To suppress the inclusion of the default assembly in the solution (and therefore the ability to delete the class file from the solution as well), update the solution property ‘Include Assembly In Package’ to False:

 image

Enjoy!

Get SharePoint to Automatically Read and Write XML With XMLParser

Have you ever wondered how the property promotion used by SharePoint to extract and write InfoPath form values to library columns works? XMLParser is the answer: http://msdn.microsoft.com/en-us/library/aa544164.aspx

You can use the exact same technique in your own solutions to read and write xml content by updating list item values. This works due to the power of content types. All hail the content type!

The content type assigned to your library will define the columns (FieldRefs) that your xml will inherit. By extending the definition of the FieldRef elements to include a Node attribute we can map the field to a xml node via XPath.

Sample content type definition that maps MyLabel and MyValue fields to XML nodes:

image

Notice that the MyValue FieldRef also includes an Aggregation attribute. These can be used to perform mathematical functions on values (see link above for list of available functions). The XPath above I’m using reaches into the XML schema of the uploaded files and identifies where in the XML files these fields should be mapped to. Here’s the very basic XML I’m using in this demo:

image

Lastly, there is one more important FieldRef that needs to be included in your content type definition:

image

This FieldRef is used by the XMLParser to ensure that the appropriate content type ID is written to the XML file. In turn, the XML files uploaded to SharePoint must also contain the <?MicrosoftWindowsSharePointServices ContentTypeID="0x0101007438f6c6e5834860a94a8284a8c7106c"?> element which is then used to identify which content type the XML file should be mapped to. The content type ID specified in the FieldRef element and the XML must match for the XMLParser to work. Finally, this FieldRef must use the ID of {4B1BF6C6-4F39-45ac-ACD5-16FE7A214E5E} – this is the internal field ID for the content type site column.

Note: Replace the content type Id in my demo with your own content type id. Do not replace the FieldRef ID.

Once your content type is fully defined, deploy it to SharePoint, create a library to host your content type and then upload an XML file that includes the <?MicrosoftWindowsSharePointServices processing instruction described above.

The results should be instantly visible, before I’ve even confirmed the document upload, the XMLParser has executed, found my processing instruction in the xml file, found the same content type assigned to my library and performed the mappings defined by my FieldRefs:

image

Now my file is uploaded, the vales extracted from the XML are available to me in the list:

image

If I update the list item, and then download the xml document, the XMLParser will ensure that any new values entered into my list item columns are then written back to the underlying XML before they are downloaded, thus ensuring the list item values and XML values remain in sync.

The full content type definition used in this demo:

image

The full sample xml used in this demo:

image

Both these files can be downloaded from my SkyDrive: https://skydrive.live.com/?cid=941d17eca8c6632d&sc=documents&uc=2&id=941D17ECA8C6632D%21351#

Enjoy!

Reduce Page Weight

Whilst working with SharePoint Master Pages is can be quite common to have to remove ContentPlaceHolders that contain content or controls that aren’t relevant to the design of the site.

If you simply remove the unwanted  <asp:ContentPlaceHolder elements from the master page, you’ll break the site and receive an error when viewing any pages that use your master page.

To work around this is can be quite common to see code that looks something like this used in custom master pages:

<div style="display:none;">
                      <asp:ContentPlaceHolder ID="PlaceHolderPageTitleInTitleArea" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderGlobalNavigation" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderGlobalNavigationSiteMap" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderPageImage" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderTitleLeftBorder" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderMiniConsole" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderTitleRightMargin" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderTitleAreaSeparator" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderNavSpacer" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderLeftNavBarBorder" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderBodyLeftBorder" runat="server"/>
</div>

In the above example, the unwanted ContentPlaceHolders are contained inside a div that is then hidden via the style attribute "display:none;". The result of this is that the page will render and the unwanted ContentPlaceHolders are not shown to the user.

However, this technique still transfers all the HTML that these ContentPlaceHolders contain down to the client and bloat the page weight with content that is never meant to be shown.

The recommended approach resolving the transmission of unnecessary HTML is to replace the div that is used to hide the unwanted content with a hidden asp Panel that runs server-side. The HTML rendered by the ContenPlaceHolders inside this Panel is never transmitted to the client and the page weight is reduced. This can increase load times and reduce load on the server which is never a bad thing!

The example below show the recommended code:

<asp:Panel visible="false" runat="server">
                      <asp:ContentPlaceHolder ID="PlaceHolderPageTitleInTitleArea" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderGlobalNavigation" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderGlobalNavigationSiteMap" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderPageImage" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderTitleLeftBorder" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderMiniConsole" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderTitleRightMargin" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderTitleAreaSeparator" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderNavSpacer" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderLeftNavBarBorder" runat="server"/>
                      <asp:ContentPlaceHolder ID="PlaceHolderBodyLeftBorder" runat="server"/>
</asp:Panel>

I hope this helps….