Thursday 20 February 2020

Multiple Download in SharePoint Classical libraries

Downloading Files

SharePoint Classical Library

I might be a bit late posting this but I found some users still use the classical style of SharePoint over the modern style. For them, one of the drawbacks of classical style is that only one file can be downloaded at a time. The bellow solution provides a way to download multiple selected documents in a SharePoint library through the user custom action.

There are many paid apps on the web for this and that is the reason why we do not get the solution for such a simple requirement.

The solution requires a custom action on the library where this is to be implemented, this can be added through SharePoint Designer using simple steps:
Open the Site in SharePoint Designer > from the Left Nav open the List and Libraries section > on the page select the Library where this solution needs to be implemented > under the List Custom Action need to add a new Custom action > to do this select the Custom Action button and from the dropdown select View Ribbon > in the Popup window add the Action Name as "Multiple Download" > in the Ribbon Location change the value from "Ribbon.Documents.Manage.Controls._children" to "Ribbon.Documents.Copies.Controls._children" > You could add the image link if you need to further decorate your Custom Action.


This is a simple Custom Action and nothing much happens when you click it, in order to customize the Custom Action you would need to further update the custom action's CommandUIExtension in order to do this you could go through any approach, but I'm showing you to update it through a simple c# console app.
create a C# console app in Visual Studio (2019 or any that you use) > Add SharePoint reference through NuGet Package Manager "Microsoft.SharePointOnline.CSOM" or simply add reference through Local SharePoint DLL files > Create ClientContext for the Site > add Credentials for the ClientContext > get the desired List/Library > Load the List's UserCustomActions > check for custom action with Name matching "Multiple Download" as mentioned earlier while creating the custom action through SharePoint designer > on the console write the current value of custom action's CommandUIExtension > store this value and modify the value's "CommandAction" and "EnabledScript" with respective values "javascript: bulkDownload();" and "javascript: enable();" these are actually the functions that would be called for this custom action, but these functions do not exist on any javascript file so you need to add a javascript link to the master page with the following javascript commands.

C# Code to edit the custom action:

`
Console.WriteLine("Started Main");
ClientContext ctx = new ClientContext();
SecureString password = new SecureString(); foreach (char c in ) { password.AppendChar(c); }
ctx.Credentials = new SharePointOnlineCredentials(password);
var web = ctx.Web;
ctx.Load(web);
ctx.ExecuteQuery();
Console.WriteLine();
Console.WriteLine(web.Title);
Console.WriteLine();
var list = web.Lists.GetByTitle();
ctx.Load(list);
ctx.ExecuteQuery();
Console.WriteLine();
Console.WriteLine(list.Title);

Console.WriteLine();
var customAntions = list.UserCustomActions;
clientContext.Load(customAntions);
clientContext.ExecuteQuery();
if (customAntions.Count > 0)
{
foreach (var customAction in customAntions)
{
if (customAction.Name != "Multiple Download")
{
#region FirstExecution
// // First execute below lise to get the current value.
Console.WriteLine(customAction.CommandUIExtension);
#endregion FirstExecution
#region SecondExecution
// // During next execution uncoment bellow two lines and execute
// customAction.CommandUIExtension = "<CommandUIExtension><CommandUIDefinitions><CommandUIDefinition Location=\"Ribbon.Documents.Copies.Controls._children\"><Button Id=\"{Same Value as Fetched}\" Command=\"{Same Value as Fetched}\" Image32by32=\"Image Url or blank\" Image16by16=\"Image Url or blank\" Sequence=\"0\" LabelText=\"Multi Download\" Description=\"\" TemplateAlias=\"o1\" /></CommandUIDefinition></CommandUIDefinitions><CommandUIHandlers><CommandUIHandler Command=\"{Same Value as Fetched}\" CommandAction=\"javascript:bulkDownload();\" EnabledScript=\"javascript:enable();\" /></CommandUIHandlers></CommandUIExtension>"; // Line one end
//customAction.Update(); 
#endregion SecondExecution
}
}
}
list.Update();
web.Update();
clientContext.Load(list.UserCustomActions);
clientContext.ExecuteQuery();
Console.WriteLine();
`

The Javascript file contents would be as follows:

`
function enable() {
    var items = SP.ListOperation.Selection.getSelectedItems();
    var itemCount = items.length;
    return (itemCount > 1);
}
function bulkDownload() {
    var items = SP.ListOperation.Selection.getSelectedItems();
    var itemCount = items.length;
    if (itemCount == 0) return;
    var context = SP.ClientContext.get_current();
    var site = context.get_site();
    var web = context.get_web();
    var list = context.get_web().get_lists().getById(SP.ListOperation.Selection.getSelectedList());
    context.load(site);
    context.load(web);
    context.load(list);
    context.executeQueryAsync(
        Function.createDelegate(this, function () {
            var query = new SP.CamlQuery();
            var view = "";
            for (var i = 0; i < itemCount; i++) {
                view += `${items[i].id}`;
            }
            view += '
';            query.set_viewXml(view);
            var url = window.location.href;
            if (url.indexOf('?') > -1) {
                var urlQ = url.split("?")[1];
                var urlQQ = urlQ && urlQ.split("&");
                var folderUrl = '';
                for (var i = 0; i < urlQQ.length; i++) {
                    var a = urlQQ[i];
                    if (a && a.length && a.indexOf("RootFolder") > -1) {
                        folderUrl = a.split("=")[1];
                    }
                }
                if (folderUrl && folderUrl.length) {
                    query.set_folderServerRelativeUrl(decodeURIComponent(folderUrl));
                }
            }
            var itemsQuery = list.getItems(query);
            context.load(itemsQuery, 'Include(FileRef)');
            context.executeQueryAsync(
                Function.createDelegate(this, function () {
                    var itemsEnu = itemsQuery.getEnumerator();
                    while (itemsEnu.moveNext()) {
                        var currentItem = itemsEnu.get_current();
                        var downloadApi = web.get_url() + '/_layouts/15/download.aspx?SourceUrl=';
                        window.open(downloadApi + encodeURIComponent(currentItem.get_item('FileRef')), "_blank");
                    }
                }),
                Function.createDelegate(this, function (sender, args) {
                    console.log("Error retriving Items");
                })
            )
        }),
        Function.createDelegate(this, function (sender, args) {
            console.log("Error loading list");
        })
    );
}
`