Adding direct access to the ECB menu in SharePoint 2013

If you are using Office 365 or SharePoint 2016, you can just use the out of the box right click functionality to open the ECB menu directly.

I already noticed that in SharePoint 2013 you now need to click twice to access the ECB menu of a list item. But yesterday at a DIWUG meeting (the Dutch SharePoint user group) I came up with the idea to fix this with some javascript. I thought it would possibly be not that hard to achive with some jQuery. So today I have spend some time to get the concept working. It seems to be working fine, but I haven't extensively tested it. Keep in mind that this is a proof of concept.

To get this working, I have added some script tags to the header of the master page. First I have added a link to jQuery, and secondly a link to a custom script file, that I have placed in the _layouts folder (you can also add it to a document library of course). After some testing, I noticed that the script worked fine only some of the time. I already noticed that some content is loaded asynchronously as part of AjaxDelta. Putting one and the other together, and doing some searching on the web, I found an article that shows how you can add a script link inside a AjaxDelta control. After using a AjaxDelta control the script worked fine every time. So adding it all up, I have added the following to the header of the master page:

<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.3.js"></script>

<SharePoint:AjaxDelta ID="DirectAccessEcbMenuJS" Container="false" runat="server">
	<script type="text/javascript" src="/_layouts/15/DirectAccessEcbMenu.js"></script>
</SharePoint:AjaxDelta>
    

Or if you use publishing, add the following to the header of the html master page template:

<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.3.js"></script>

<!--SPM:<SharePoint:AjaxDelta ID="DirectAccessEcbMenuJS" Container="false" runat="server">-->
	<script type="text/javascript" src="/_layouts/15/DirectAccessEcbMenu.js"></script>
<!--SPM:</SharePoint:AjaxDelta>-->
    

Using the IE developer tools (F12) I looked at the html and scripts used to open the Callout menu and the ECB menu. Using some jQuery I iterated all list item link anchors (ms-lstItmLinkAnchor), and added a new piece of html that opens the ECB. I found out that the parent element of the 'a' element that contained the javascript to open the ECB needed two attributes, one containing the item id, and one containing a ctxname. I have used the id of the parent table row to extract these values, as I noticed this id could be comma separated to gain the needed values. I edited the html that would be inserted, and looked for a standard SharePoint icon that looked ok. Then I tackled the table heading that was now not in sync with the newly added column. Putting it all together I came up with the following script:

$(document).ready(function() {
    AddDirectAccessEcbMenu();
});

function AddDirectAccessEcbMenu() {
    var fixedTable = null;
    $(".ms-lstItmLinkAnchor").each(function () {
        var anchor = $(this);
        if (anchor.get(0).onclick && anchor.get(0).onclick.toString().indexOf("OpenCalloutAndSelectItem") >= 0 && !el.atemvisited) {
            el.atemvisited = true;
            var tr = anchor.closest("tr");
            var td = anchor.closest("td");
            var img = anchor.find("img");
            var trid = tr.attr("id");
            if (trid) {
                var values = trid.split(',');
                var alt = img.attr("alt");
                var open = jq('<td class="' + td.attr("class") + '"><span class="js-callout-ecbMenu" id="' + values[1] + '" eventtype="" perm="0x7fffffffffffffff" field="LinkFilename" ctxname="ctx' + values[0] + '"><a onclick="HandleDocumentBodyClick();calloutCreateAjaxMenu(event); return false;" href="#" class="js-callout-action ms-calloutLinkEnabled ms-calloutLink js-ellipsis25-a"><img alt="' + alt + '" src="/_layouts/15/images/TPMAX1.GIF"></a></span></td>');
                open.insertAfter(td);
                var table = tr.closest("table");
                var tableElement = table.get(0);
                if (fixedTable != tableElement) {
                    fixedTable = tableElement;
                    var index = td.index();
                    table.find("th:nth-child(" + index + ")").attr("colspan", 2);
                }
            }
        }
    });
}
    

That is all there was to it. Ok I didn't get there in one go, and the javascript might not be the most efficient code that can be created (just did an update to the script to improve efficiency, should be better now), still I think this is a good proof of concept of how you can add direct access to the ECB menu to please the user. You can see how it looks in the following screenshot:

Screenshot ECB menu

I hope that this shows that if you are creative you can do a lot of crazy things to improve the user experience. If you ever find yourself in a position where you hear your users complaining that they don't like the way that the ECB menu needs to be accessed in SharePoint 2013, you now know that this can be fixed using a little bit of javascript.

Update:

Today (11/9/2013) I have updated the above script, fixing an issue with the previous code when opening the ECB menu when an other ECB menu was already open. This stopped the newly opened menu form working correctly. I have added a call to 'HandleDocumentBodyClick();' before the call to open the new ECB menu to fix this issue. An other thing I would like to mention is that you can also use '_spBodyOnLoadFunctions.push(AddDirectAccessEcbMenu);' instead of the jQuery $(document).ready code.

Update:

Today (5/29/2014) I have updated the above script, as it only makes sense to add this direct access if the ellipsis opens the callout menu. So I added a check for just that, and also updated the sandboxed wsp created two days ago that contains this script:

if (anchor.get(0).onclick && anchor.get(0).onclick.toString().indexOf("OpenCalloutAndSelectItem") >= 0) {
    

Also added a quick td class copy, to ensure the same styling. And I noticed that I sometimes had to do a refresh to see the direct access. This was due to the minimal download stategy. So I now also included a fix for that, based on: The Effect of SharePoint 2013′s Minimal Download Strategy (MDS)

function AlsoRunAfterMinimalDownload() {
    if (window.asyncDeltaManager && window.asyncDeltaManager.add_endRequest) {
        asyncDeltaManager.add_endRequest(function () {
            AddDirectAccessEcbMenu();
        });
    }
}

_spBodyOnLoadFunctions.push(AlsoRunAfterMinimalDownload);
    

Update:

Today (6/8/2014) I have updated the Smartloading script to check if another Smartloading script already added the jQuery link. As I and maybe others will use this to ensure jQuery is loaded, and you only need jQuery added once. Also I have updated jQuery that is loaded when no jQuery is found, to version 1.11.0. I have updated the .js and .wsp that you can download.

Update:

Today (7/8/2014) I have updated the script to include a fix. This is a fix for when the view of the documents is getting sorted, or if a document gets uploaded (drag and drop), the direct access disappeared when this was done. With this fix, the direct access to the ECB will be applied after sorting or upload. Also I changed the jQuery version to 1.11.1. If you already installed the solution, just download the .js file below and update the file in your Style Library.

Smartloading jQuery:

Today (5/27/2014) I was busy with some javascript, and this script came into the back of my mind. I was looking at an intelligent way to deal with jQuery. As sometimes jQuery is already included, sometimes it's not, and other times it is included with the noConflict method.

You would want a solution that works independent of how jQuery is included, and that is flexible to changes made by a designer or the IT department. Now I have added some script to implement these demands. As this script is smart in dealing with jQuery, it's now also possible to add the direct access to the ECB menu in a Sandboxed Solution (no assemblies needed, so it never uses any server resources). Just add the solution, and activate it, and you have direct access to the ECB on your whole site collection.

So what was needed was:

function SmartJqueryInit() {
    if (!window.jQuery) {
        for (var prop in window) {
            var p = window[prop];
            if (p && p.fn && p.fn.jquery) {
                window.SmartJquery = p;
                break;
            }
        }
        if (!window.SmartJquery && !window._isSmartJqueryLoading) {
            window._isSmartJqueryLoading = true;
            var newscript = document.createElement('script');
            newscript.type = 'text/javascript';
            newscript.src = '//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.2.min.js';
            (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(newscript);
        }
    }
    else
        window.SmartJquery = jQuery;
}

_spBodyOnLoadFunctions.push(SmartJqueryInit);
    

Also, in the AddDirectAccessEcbMenu method, you need to account for the possibility that jQuery was not included on the page, and that the jQuery that was added by the script, has not finished loading. So you need to add some code to this method:

if (!window.jQuery && !window.SmartJquery) {
    setTimeout(AddDirectAccessEcbMenu, 100);
    return;
}
var jq = (window.jQuery || window.SmartJquery);
    

Instead of using $ you now replace this with the local variable called jq.

You can now download the Sandboxed Solution: DirectEcbMenuAccessSandbox.wsp
The stand alone javascript is also available for download: DirectAccessEcbMenu.js

Update:

Today (3/21/2015) I have updated the script with a minor update. This is due to an issue with using the dynamic loading of jQuery when your site is on https, as I use the CDN url with http, you get a message about insecure content, that is why I now changed the following line of code:

        // old version
        newscript.src = 'http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.0.min.js';

        // improved version
        newscript.src = '//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.2.min.js';
    

When using the // in the url without http or https scheme, the currently used scheme will be applied, so the url will be relative to the current scheme. As CDN's have both http and https available, you will no longer get the insecure content message. I have updated the script file and the wsp accordingly, using the 1.11.2 version of jQuery. I also updated the text above to use // instead of http://. There are other methods for doing this, but I think this is a clean way of supporting multiple schemes.

Update 6/3/2016

I have noticed an issue with office web apps and javascripts added with a scriptlink. When debugging script for a Word Add-In I noticed error messages ReferenceError: _spBodyOnLoadFunctions is not defined. I also found that also my own scripts gave out these errors. Now I have updated everything with a check if _spBodyOnLoadFunctions is available, before adding functions to this array. If you already installed the solution, please update the javascript file in the Style Library.

Revision of text from 5/23/2013