1

I'm trying to use a script to remove multiple list items from multiple lists. I'm using Javascript because I don't have access to Visual Studio.

The idea is there's a Parent List item in a Document Library, called a Tender, and 4 associated lists with related entries. Some of them are lookups, some of them are just text fields that have a field that matches a key value in the Tender entry.

I have already written a function that seemed to delete all items matching the field, "Collective #" (display name) or "Collective_0020__0023_" (internal name) from any specific list. My next imagined step was to call the method once for each list containing items to be deleted. This didn't work! I have a feeling it has to something to do with Asynchronous programming (something I don't understand) that prevents the method from deleting items from more than just the list from the first function call.

I had originally tried calling the method five times, in a for loop, like so:

for (var i in lists) {
    deleteListItemsByCollectiveNum(lists[i], strCollectiveNum);
}

thinking it would delete from one, then the next, then the next, etc. This didn't work.

My next iteration of the process was to call the deleteListItemByCollectiveNum method recursively. Such as shown in the code below. This has exactly the same results.

Here's the updated code, as of June 27, 2013:

function deleteTenderAndAssociatedItems(strCollectiveNum) {

// List of all Lists/Libraries to be included in the clean up
// Tenders is last because it's the parent.
var lists = [
    "Tenders - Add Docs",
    "Tender Addenda",
    "Tender Tasks",
    "Tender Log",
    "Tenders"
];

var confirmMessage = "Are you sure you want to remove this Tender and all associated data from SpecTrac?";

var position = 0;
//start by deleting from the first list
deleteListItemsByCollectiveNum(lists[position], strCollectiveNum);

// Deletes all list items from a chosen list with Collective #'s matching strCollectiveNum parameter. 
// The intention is to execute this method for each list that holds entries related to a specific Tender.
//
// Params:
// strListTitle - The Display name of the list or library that has a field externally displayed as "Collective #" 
//                or Internally as "Collective_0020__0023_" (unicode)
// strCollectiveNum - The Collective Number of the items you wish to delete.
//
function deleteListItemsByCollectiveNum(strListTitle, strCollectiveNum) {       

    //setup
    var context = null;
    var listRef = null; 

    var itemQuery = null
    var itemQueryString = "";
    var itemCollection = null;

    var startCount = 0;
    var endCount = 0;

    //Get the Site Context - This is what we query for references to site objects.
    var context = new SP.ClientContext.get_current(); 

    //Get reference to list passed as string parameter
    listRef = context.get_web().get_lists().getByTitle(strListTitle);

    //init the query object             
    itemQuery = new SP.CamlQuery();

    //build the query string
    //Equiv to SELECT * FROM list WHERE CollectiveNum = strCollectiveNum (parameter)
    itemQueryString = '<View><Query><Where>';
    itemQueryString +='<Eq><FieldRef Name=\'Collective_x0020__x0023_\'/><Value Type=\'Text\'>' + strCollectiveNum + '</Value></Eq>';
    itemQueryString +='<Eq><FieldRef Name=\'Collective_x0020__x0023_\'/><Value Type=\'Lookup\'>' + strCollectiveNum + '</Value></Eq>';
    itemQueryString +='</Where></Query></View>';

    //set the query string
    itemQuery.set_viewXml(itemQueryString);

    //Get the a collection of items that match strCollectiveNum
    //"Execute the query"
    itemCollection = listRef.getItems(itemQuery);       

    //Load these items and grab them from the context object.
    //parameters define which methods will be executed at Success (param 1) and Failure (param 2);
    context.load(listRef);
    context.load(itemCollection);

//The first time we get here it continues to the deleteObjects method just fine.
//However, during the first _recursive_ function call this code below does _nothing_
//Debugger hits the breakpoint, I press continue, and next nothing. No success, no fail.
//This is significant.
    context.executeQueryAsync(deleteObjects, fail);

    //delete all objects when the query returns successfully
    function deleteObjects() {
        //get the item count.  Used in debugging.
        startCount = listRef.get_itemCount();
        var modifiedCount = 0;

        //The enumerator object let's us cycle through all items from the returned query on the list above.
        var resultsEnumerator = itemCollection.getEnumerator();

        var items = [];

        //Deleting an object alters the enumerator in a fashion that prevents us from using the while loop properly.
        //When we find one of the objects push it into a regular javascript array to be deleted.
        while (resultsEnumerator.moveNext()) {
            var currentItem = resultsEnumerator.get_current();
            items.push(currentItem);
        }       

        //Delete all items found.
        for(i = 0; i < items.length; i++) {
            items[i].deleteObject();
            modifiedCount++;
        }

//What I've noticed is that if there have been no items deleted when the query is executed it simply falls through, no success call, no fail call.  
//So if the list has gone unmodified go on to the next step/piece, in this case execute success()
        if (modifiedCount > 0) {
            //Update the list after doing these deletes.
            listRef.update()
            context.load(listRef);
            context.executeQueryAsync(success, fail);
        } else {
            success();
        }
    }

    //run upon query success
    function success() {
        //Get the end item count and show it next to the original item count to verify items were deleted.
        endCount = listRef.get_itemCount();
        alert("start " + startCount + " end " + endCount);

        if (position < lists.length) {
            position++;             
            //recursive call;
            deleteListItemsByCollectiveNum(lists[position], strCollectiveNum);
        }
    }
    //Query failed for some reason. Show the exception.
    function fail(sender, args) {
        alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());  
    }
}   

}

I'm sure there's something I'm not understanding about Asynchronous method calls.

How is it I can utilize the method I've created to delete all items matching the field from all five lists?

I've also put my code into a tinypaste in case you prefer to read a version with nicer tabs/indenting: http://tny.cz/188aa886

Update June 27, 2013:

I've modified the code as per people's suggestions:
- I've fixed the spelling mistake (now "Item*s*" instead of "Item") in the recursive method call (why didn't this give an error before?)
- I've removed Function.createDelegate() as per a commenter's suggestion, not replacing with a closure but just a regular method call.
- I've made modifications to the deleteObjects() method to account for situations where a list isn't modified.

I've also tried altering the order of the items in the Lists array, thinking that perhaps a different list would behave differently (perhaps because of my query that tests for a regular text field OR a lookup field?).

The first context.executeQueryAsync(deleteObjects, fail) executes just fine, I get an alert showing me an item has been deleted ("start 2 end 1") and the recursive call goes through, it starts all over again. However! At this point the second async query call does nothing. It just stops. No call to deleteObjects() OR fail()

This is something significant. Any other ideas what could be happening? I still feel like there's a piece of the puzzle in regards to asynchronous calls that hasn't been touched up on yet. But.. Of course I don't know :)

Here's a simplified chunk of code to illustrate my problem more clearly:

function test() {
lists = ["Tenders - Add Docs", "Tenders"];
var index = 0;
show_counts(lists[index]);      

function show_counts(strListTitle) {

    var context = new SP.ClientContext.get_current();       
    var listRef = context.get_web().get_lists().getByTitle(strListTitle);

    var queryString = "<Query><OrderBy><FieldRef Name='ID' /></OrderBy></Query>";       
    var itemQuery = new SP.CamlQuery();
    itemQuery.set_viewXml(queryString);

    var itemCollection = listRef.getItems(itemQuery);       
    context.load(itemCollection);

    context.executeQueryAsync(function() {      
        alert(strListTitle + " " + itemCollection.get_count());
        while(index < lists.length-1) {
          index++;
          show_counts(lists[index]);
        }
    },

    function () {
        alert("failed");
    });
}

}

The query is basic - It returns all items ordered by ID. I face the same problem with this test code as I do with my actual code. The query runs the first time, shows me an alert: "(Name of List) (ItemCount)", recursively calls show_count(next list in lists array) and the second executeQueryAsync call simply does nothing.

Can anyone explain this behavior? This is my first touch of asynchronous programming. I thought I understood the basics but it seems to me I'm missing something fundamental.

1
  • I have added code to delete multiple items from multiple lists to my answer. I confirmed that it worked via running it in the console of Chrome while browsing my dev site. Jun 27, 2013 at 17:50

2 Answers 2

3

If I understand your question, you are trying to figure out how to delete from a queue that represents a related group of SharePoint lists. It's not clear to me, though, are you actually having an issue?

Here is an example of how to perform asynchronous operations on a queue of uncertain length.

var queue = ["listOne", "listTwo", "listThree"];

var deleteBatchItems = function(index){
      //Whatever you want to call the function. I'm sure you get the idea.
      //define your context and other variables as you see fit.
      //I am omitting that here for brevity/clarity

      listRef = context.get_web().get_lists().getByTitle(queue[index]);

      //set your callbacks up. The important thing is that your call back
      //will check if there is another list in the queue.

      context.executeQueryAsync(function(){
         index++; //Update the index to go to the next
         if (queue[index]){
             deleteBatchItems(index):
         }

      },function(){
         //What do you do if it fails? Stop everything? 
         //Create an item in another list to log it?
      });
}

var index = 0;
deleteBatchItems(index);

While there are some stylistic differences, it looks to me like both examples (yours and mine) do pretty much the same thing. So I am not sure if you are having a concrete error or not...

Try to not nest all of your functions. Here is some code I just tested in the console of my browser and it works.

var nameSpace = nameSpace || {};
nameSpace.sp = {};
var queue = ["PAR_Tasks", "PAR_IT_Tasks", "PAR_OP_Tasks"];
nameSpace.sp.context = new SP.ClientContext("/sites/plm");
nameSpace.sp.web = nameSpace.sp.context.get_web();
var index = 0;

function addSomeItems(index){
    var itemOptions = {listTitle: queue[index]};
    itemOptions.fieldValues = {}; 
    itemOptions.fieldValues["Title"] = "Test item: " + index;
    itemOptions.successCallback = function(){
        console.log("Success!");
        index++; //Update the index to go to the next
         if (queue[index]){
             addSomeItems(index);
         }
    };
    itemOptions.failureCallback = function(sender,args){
        console.log("Opps: " + args.get_message()); 
    }
    nameSpace.sp.createListItem(itemOptions);
}


nameSpace.sp.createListItem = function(listItemOptions){
                var list = nameSpace.sp.web.get_lists().getByTitle(listItemOptions.listTitle);
                var _listItem = list.addItem(new SP.ListCreationInformation());

                for (var index in listItemOptions.fieldValues) {
                    _listItem.set_item(index, listItemOptions.fieldValues[index]);
                }
                _listItem.update();

                nameSpace.sp.context.executeQueryAsync(
                                                function (sender, args) {
                                                    if (typeof (listItemOptions.successCallback) === "function") {
                                                        listItemOptions.successCallback(sender, args);
                                                    }
                                                },
                                                function (sender, args) {
                                                    if (typeof (listItemOptions.failureCallback) === "function") {
                                                        listItemOptions.failureCallback(sender, args);
                                                    }
                                                });
};

I will now try and do this with a delete and I will post it.

This code worked for me.

var nameSpace = nameSpace || {};
nameSpace.sp = {};
var queue = ["PAR_Tasks", "PAR_IT_Tasks", "PAR_OP_Tasks"];
nameSpace.sp.context = new SP.ClientContext("/sites/plm");
nameSpace.sp.web = nameSpace.sp.context.get_web();
var index = 0;
var queryXml = '<View><Query><Where><Contains><FieldRef Name="Title" /><Value Type="Text">Test item</Value></Contains></Where></Query></View>';

function deleteSomeItems(index){
    var itemOptions = {listTitle: queue[index]};
    itemOptions.successCallback = function(){
        console.log("Success!");
        index++; //Update the index to go to the next
         if (queue[index]){
             deleteSomeItems(index);
         }
    };
    itemOptions.failureCallback = function(sender,args){
        console.log("Opps: " + args.get_message()); 
    }
    nameSpace.sp.deleteListItems(itemOptions);
}


nameSpace.sp.deleteListItems = function(listItemOptions){
            var listItemOptions = listItemOptions;
            var list = nameSpace.sp.web.get_lists().getByTitle(listItemOptions.listTitle);
            var query = new SP.CamlQuery();
            query.set_viewXml(queryXml);
            nameSpace.sp.items = list.getItems(query);
            nameSpace.sp.context.load(nameSpace.sp.items);
            nameSpace.sp.context.executeQueryAsync(
                    function (sender, args) {
                        console.log("Now we delete.");
                        var resultsEnumerator = nameSpace.sp.items.getEnumerator();
                        nameSpace.sp.itemsToDelete = [];
                        while (resultsEnumerator.moveNext()) {
                        console.log("Go!");
                            var currentItem = resultsEnumerator.get_current();
                            nameSpace.sp.itemsToDelete.push(currentItem);
                        }       

                        //Delete all items found.
                        for(i = 0; i < nameSpace.sp.itemsToDelete.length; i++) {
                            nameSpace.sp.itemsToDelete[i].deleteObject();
                        }
                        //nameSpace.sp.context.load(nameSpace.sp.items);
                        nameSpace.sp.context.executeQueryAsync(function(){
                            listItemOptions.successCallback();
                        },function(sender, args){
                         console.log("Error loading deleted items"+args.get_message());
                            listItemOptions.failureCallback();
                        });
                    },

                    function (sender, args) {
                        listItemOptions.failureCallback(sender, args);
                    });
};
10
  • This shouldn't be a problem. If a list.length = 5 then it's indices are 0 through 4. The problem I'm having is that the second time around executeQueryAsync simply doesn't run. It gets to that line of code then returns, finishing with only the first query executing, deleting items from only the first list. In other words it fails to execute either the success or the failure delegate methods.
    – adnauseam
    Jun 26, 2013 at 20:46
  • Try wrapping the function calls in an anonymous as in my example rather than using createDelegate. I have had issues with that in my own code as well. Jun 26, 2013 at 22:31
  • 1
    I've just tried your code. After adapting it to fit my site here it worked very briefly. Upon adding the remaining lists into the queue array the function now fails, and fails again if I change it back to the working version I adapted. I can mark yours as correct as it has worked, but there's something beyond me going on at my end. I really don't know what to say. Sometimes the scripts work the next minute they don't, and this is all without changing anything.
    – adnauseam
    Jun 27, 2013 at 19:02
  • Consider opening another question regarding that issue. I would be very interested in knowing what you find as I use JS CSOM pretty heavily. Jun 27, 2013 at 19:44
  • 1
    Just a side note if you use IE, it will break if you use console.log, the console variable is loaded in IE when you hit F12. Have burned myself on that before, everything worked, and got back and it all was broken because the console variable wasnt there Jul 2, 2013 at 11:58
0

Haven't tested your code, but you say you run it recursively. At first glance I see in your success function you call: deleteListItemByCollectiveNum not deleteListItemsByCollectiveNum. Missing a 's' in Items

1
  • Yes, this is true. Unfortunately this doesn't fix my woes :(
    – adnauseam
    Jun 27, 2013 at 14:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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