14

We have implemented an issue management system in SharePoint 2013 which provisions a page for every issue added to a specific list.

One of the Web Parts the automatically provisioned page contains is the Document Web Part. We currently have the functionality that when a user adds a document to the Web Part using the 'New Document' button it is assigned an issue number relative to the page they are on. This is so we can filter the document Web Part to only show documents that are applicable to the issue.

There is one document library for all documents for all issues.

The Drag and Drop functionality is tripping us up. We cant find a way to enforce the addition of the issue number to the document. We also cant find a way to disable it either.

Has anyone managed to enforce a user to add a metadata to an item they drag and drop? Alternatively is there a way to disable the drag and drop functionality?

1
  • Hi This is a great concept. Are you able to post the solution here?
    – user24221
    Mar 23, 2014 at 22:57

7 Answers 7

12

This has been an issue for a long time, multiple file upload has the same problem in that you cannot apply metadata to items when uploading more than one at a time. The work around is to make the desired metadata fields required. This forces the files to remain checked out to the uploader until the required fields are populated and checked in.

You could possibly utilize jQuery UI to hook into the drop event and then look at the page to get the proper ID then use web service updates to do a bulk metadata update of the files. I haven't seen any examples of this in the wild, but it sounds feasible in theory.

2
  • I thought that might be the case. We took the easier option and disabled drag and drop. We did this by duplicating the dragdrop.js file in the layouts folder and removed the content from some of the methods as to overwrite the existing functionality.
    – boondawg
    Nov 11, 2013 at 3:29
  • With SharePoint Online, even making the metadata column as required does not help. Here is an article explaining to exact same situation and a resoltuion to it. computerworld.com/article/3045118/microsoft-subnet/…
    – Ashish
    Jul 14, 2017 at 6:10
18

I came across this while searching and figured I would add my thoughts. I have also been looking for a way to tackle this problem.

The DragDrop.js file in Layouts has a function called UploadProgressFunc which is essentially just a switch statement. One of the options on that switch is uploaded. This is the function to tap into if you need to handle this differently.

function UploadProgressFunc(percentDone, timeElapsed, state) {
    state.percentDone = percentDone;
    var messageType = ProgressMessage.EMPTY;

    switch (state.status) {
        case 1:
            messageType = ProgressMessage.VALIDATION;
            break;
        case 3:
            messageType = ProgressMessage.UPLOADING;
            break;
        case 4:
            messageType = ProgressMessage.UPLOADED;
            MyNewFunction();
            break;
        case 5:
            messageType = ProgressMessage.CANCELLED;
            break;
    }
    UpdateProgressBar(messageType, state);
}

I created a separate js file called DragDrop.Extended.js, it has a copy of the UploadProgressFunc function which simply calls into a new function after setting the messageType on case 4.

This is to use Javascripts behaviour of overwriting existing functions with the new one when a duplicate loads. Doing so means I do not have to edit any OOTB files. I can just run this snippet of js in my master page.

window.onload = function(){
        setTimeout( function(){
            if(typeof UploadProgressFunc != 'undefined'){
                $.getScript("path_to_my_custom_js_file/DragDrop.Extended.js");
            }
        }, 4000);
 };

Now as it is on master page it does run for ever page. I am hoping to eventually get a better fix. The code checks if the dragdrop.js file has loaded by looking to see if the function is undefined. If it has loaded then it also loads in my file to overwrite that function.

The new function being used currently only handles 1 file at a time, I am working on making it handle multiple files at present. The new function called uses the JSOM to get information about the uploaded file. It then creates a call to to the list to retrieve the ID of the uploaded file. This is needed as it never bring that ID down to the context during upload.

With the ID and the details available in the context I am able to construct the EditForm URL for that item. I then just pass that EditForm URL into a SP Dialog.

var options = SP.UI.$create_DialogOptions();
options.title = "Add File Metadata";
options.url = editFormAddress;
options.autoSize = true;

SP.UI.ModalDialog.showModalDialog(options);

With all of this in place, after a user drags an item on to the document library, a dialog box then appears for them to add in the Metadata. It is a starting point for now. I am currently looking at making this work for multiple files and looking at a more elegant solution for loading the overwriting function script.

//edit

To get the ID(s) of uploaded files I used JSOM and queried the document library with a CAML query. You can get the library name from the context and the current users username. With those details I just search files uploaded by the user in the last few minutes.I then built a JS array of edit urls.

I then loaded in custom buttons to the modal using javascript, they cycled back and forward through the array of edit URLs built with the query results. I had to override the submit function too otherwise it closed the window after saving one. I removed the edit URLs from array after each save.

I no longer work on that project so apologies for lack of code sample in the edit.

3
  • @PirateEric This may (or maybe not) be of interest.
    – Lotok
    Dec 23, 2013 at 16:25
  • This works for SPO?
    – yngrdyn
    Aug 5, 2015 at 14:14
  • Is there a solution for the use in sub folders? It's not working there :-(
    – Dave
    Aug 20, 2018 at 15:14
15

Here's a refectored extended version of the code found in the other answers.

Features are:

  • Open EditForm after Drag&Drop
  • Open EditForm for multiple files

(function (_window) {
    var maxTimeForReplaceUploadProgressFunc = 10000;
    function replaceUploadProgressFunc() {
        if (typeof _window.UploadProgressFunc != 'undefined') {
            _window.Base_UploadProgressFunc = _window.UploadProgressFunc;
            _window.UploadProgressFunc = Custom_UploadProgressFunc;
            console.log('replaced dialog');
        } else if (maxTimeForReplaceUploadProgressFunc > 0) {
            maxTimeForReplaceUploadProgressFunc -= 100;
            setTimeout(replaceUploadProgressFunc, 100);
        }
    }
    setTimeout(replaceUploadProgressFunc, 100);


    function Custom_UploadProgressFunc(percentDone, timeElapsed, state) {
        _window.Base_UploadProgressFunc(percentDone, timeElapsed, state);
        var messageType = ProgressMessage.EMPTY;
        switch (state.status) {
            case 1:
                messageType = ProgressMessage.VALIDATION;
                break;
            case 3:
                messageType = ProgressMessage.UPLOADING;
                break;
            case 4:
                messageType = ProgressMessage.UPLOADED;
                OpenEditFormForLastItem(state);
                break;
            case 5:
                messageType = ProgressMessage.CANCELLED;
                break;
        }

        function OpenEditFormForLastItem(state) {
            var caml = '';
            caml += "<View>";
            caml += "<Query>";
            caml += "<Where>";

            if (state.files.length > 1) {
                caml += "<In>";
                caml += "<FieldRef Name='FileLeafRef'/>";
                caml += "<Values>";
            } else {
                caml += "<Eq>";
                caml += "<FieldRef Name='FileLeafRef'/>";
            }

            state.files.forEach(function (file) {
                //only succesfull uploaded files that arent overwrites
                console.log(file);
                if (file.status === 5 /*&& !file.overwrite*/) {
                    caml += "<Value Type='File'>" + file.fileName + "</Value>";
                }
            }, this);

            if (state.files.length > 1) {
                caml += "</Values>";
                caml += "</In>";
            } else {
                caml += "</Eq>";
            }

            caml += "</Where>";
            caml += "<OrderBy><FieldRef Name='ID' Ascending='True' /></OrderBy>";
            caml += "</Query>";
            caml += "<ViewFields><FieldRef Name='ID' /></ViewFields>";
            caml += "<RowLimit>500</RowLimit>";
            caml += "</View>";
            console.log(caml);

            var cntxt = SP.ClientContext.get_current();
            var web = cntxt.get_web();
            var list = web.get_lists().getByTitle(window.ctx.ListTitle);
            var query = new SP.CamlQuery();
            query.set_viewXml(caml);
            var items = list.getItems(query);
            cntxt.load(list, 'DefaultEditFormUrl');
            cntxt.load(items);
            cntxt.executeQueryAsync(function () {
                var listEnumerator = items.getEnumerator();
                function openEditForItem() {
                    if (listEnumerator.moveNext()) {
                        var item = listEnumerator.get_current();
                        var id = item.get_id();

                        var options = SP.UI.$create_DialogOptions();
                        options.title = "Add File Metadata";
                        options.url = list.get_defaultEditFormUrl() + '?ID=' + id;
                        options.autoSize = true;
                        options.dialogReturnValueCallback = openEditForItem;
                        SP.UI.ModalDialog.showModalDialog(options);
                    } else {
                        location.reload();
                    }
                }
                openEditForItem();
            }, function (error, args) {
                    console.log("failed to get new uploaded items");
                    console.log(error);
                    console.log(args);
                });
        }
    }
})(window);
1
  • 1
    This has helped me a lot. The only things I would add, are 2. I have included your code in a function, and after DOM is loaded, I make the following condition before calling your code: After the loading of the page, I would: if(ctx!=null && ctx.listTemplate!=null && ctx.listTemplate=="101"). The reason is that we want to execute it only in document libraries. Another addition would be to this line, so that it protects the file, not only saving it: options.url = list.get_defaultEditFormUrl() + '?Mode=Upload&CheckInComment=&ID=' + id;
    – netadictos
    May 8, 2017 at 12:54
4

Apply metadata to documents when drag and drop a file. There is a bit of hardcoding, but here is what I did and it seems to be working. I am still testing this. If you have a way to get the current listname at runtime that would be brilliant! I have this in a CEWP on the library allitems.aspx page itself. I will move this to masterpage after more testing. You can also change the caml to get the list items.

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js" type="text/javascript"></script><script unselectable="on">
 window.onload = function(){ 
 setTimeout(  function(){ 
 if(typeof UploadProgressFunc != "undefined"){ 
 $.getScript("path_to_my_custom_js_file/DragDrop.Extended.js"); 
 } 
 }, 4000); 
 }; 


    function MyNewFunction() { 


        var caml = "<View><Query><Where>"
            + "<Eq><FieldRef Name='FSObjType' /><Value Type='int'>0</Value></Eq>"
            + "</Where>" 
            + "<OrderBy><FieldRef Name='ID' Ascending='False' /></OrderBy>" 
            + "</Query>"
            + "<ViewFields><FieldRef Name='ID' /></ViewFields>"
            + "<RowLimit>1</RowLimit>"
            + "</View>";


    var ctx = SP.ClientContext.get_current()
    var web = ctx.get_web();
    var list = web.get_lists().getByTitle("test1")
    var query = new SP.CamlQuery();
    query.set_viewXml(caml);
    var items = list.getItems(query);
    ctx.load(items)
    ctx.executeQueryAsync(function() {
        var enumerator = items.getEnumerator();
        enumerator.moveNext();
        var item = enumerator.get_current();
        var id = item.get_id();
        //alert(id);
        _MyGotoEditForm(id)
    }, function() { 
        //failure handling comes here
        alert("failed"); 
    });

       } 

       function _MyGotoEditForm(id) { 
       var options = SP.UI.$create_DialogOptions(); 
       options.title = "Add File Metadata"; 
       options.url = "http://**SITENAME**/**LIBRARYNAME**/Forms/EditForm.aspx?ID="+id; 
       options.autoSize = true; SP.UI.ModalDialog.showModalDialog(options); } 
1
  • Did you add your code the default display form or the default edit form?
    – Gokul N K
    May 26, 2015 at 10:55
2

I would like to submit my solution, as it cuts out one of the issues present in all of the other solutions: determining which files were just uploaded. It turns out, that we can hook into a different spot by monkey patching the CheckFilesCheckedOut function within DragDrop.js.

ExecuteOrDelayUntilScriptLoaded(
        ReplaceDragDropCheckFilesCheckedOut
    , "dragdrop.js");

var ReplaceDragDropCheckFilesCheckedOut = function () {
            if (CheckFilesCheckedOut) {
                CheckFilesCheckedOut = CheckFilesCheckedOut_Custom
            }
        };
var CheckFilesCheckedOut_Custom = function (updatedRows, state) {
            if (!updatedRows || !updatedRows.length || updatedRows.length < 1)
                return;
            var checkedOutCount = 0;
            // g_currentCtx.listName is the list guid wrapped with { }
            var listId = g_currentCtx.listName,
                itemIds = [];
            // each row here appears to be an SP.File object, with generic properties like ID, FSObjType, CheckOutUser, fileLeafRef, and a bunch more
            for (var i = 0; i < updatedRows.length; i++) {
                var newrow = updatedRows[i];

                if (newrow.CheckoutUser && newrow.CheckoutUser != "") {
                    checkedOutCount++;
                    // Fill an array with the newly uploaded file IDs
                    itemIds.push(newrow.ID);
                }
            }
            if (checkedOutCount > 0) {
                state.checkedOutCount = checkedOutCount;
                if (state.status == UploadStatus.FINISHED) {
                    UpdateProgressBar(ProgressMessage.UPLOADED, state);
                    // Do something.... in my instance I am opening an app page in a modal dialog
                    if (_spPageContextInfo) {
                        var opts = {
                            title: 'Apply metadata and Check-In...',
                            autosize: true,
                            url: _spPageContextInfo.webAbsoluteUrl + "/myApp/myAppPage.aspx?" + "SPHostUrl=" + _spPageContextInfo.webAbsoluteUrl + "&SPListItemId=" + itemIds.join(',') + "&SPListId=" + listId + "&Source=DragDrop",
                            dialogReturnValueCallback: function(dialogResult, returnValue) {
                                // Do something with the return; I'm just refreshing the parent page                                      
                                SP.UI.ModalDialog.RefreshPage(SP.UI.DialogResult.OK);
                            }
                        };
                        SP.UI.ModalDialog.showModalDialog(opts);
                    }
                }
            }
        };

Bonus tip if you're dealing with an app... to close the modal dialog from within the app page, you have to perform a postmessage to the parent:

window.parent.postMessage('CloseDialog', "<hostWeb URL>");
1

I was able to get the ID although the issue is multiple file drops. As per above example you can just redirect to the datasheet view sorted by ID descending. this way they can update all the newly updated files.

function buildmypath() {

var path = window.location.pathname;
var patharray = path.split('/');
var pathObj= new Array();


for(var i = patharray.length; i--;) {

    if (patharray[i] != 'Forms') {
        patharray.splice(i, 1);                
    }         
    else{
        break;
    }         
}     
var pathlength = patharray.length;  
var pathDocLib = patharray[pathlength-2];  
pathObj[0] = pathDocLib;

patharray[pathlength] = "datasheet.aspx"; //hardcoded datasheetview name

var newPathname = "";
for ( i = 1; i < patharray.length; i++ ) {
    newPathname += "/"
    newPathname += patharray[i];
}
 window.open(newPathname);  //navigate to datasheet 

}

0
  1. Go to Site Settings > Site Content Types
  2. Create Site Content Type with Required Values
  3. In your doc lib, set this new content type as default content type
  4. Drag and Drop a file, it will keep the new file checked out to you and ask to provide required fields values in order to check in

Not the answer you're looking for? Browse other questions tagged or ask your own question.