1

I need to use JSOM to copy a set of items (found from a CAML query) from one SharePoint list to another.

I'm pretty experienced doing this type of thing on the server side, but relatively new with it on the client side, with the async model. Based on my research, I need to use promises to do this, since I need to know when the copy is complete and whether or not any errors occurred.

I also need to copy the items one at a time, rather than batching them, because there can be a large number of items to copy, with many fields, and I run into message size errors from SharePoint if I try to do them all as one batch.

Below is a snippet of my code (with some irrelevant things removed). This appears to be working - except for one thing. As it does the copy of the items, I update a div on the page with the item number (just a counter) being copied as a progress indicator. Two things I notice:

  1. The div is not updated with the counter sequentially. With the promise, I guess I expected to see 1, 2, 3, 4... but the numbers jump around, not sequential. Not a big deal, just not what I expected.

  2. This is more of an issue. In my code, I have a function updateSuccessFinal that I call after the .done(), which calls alert() to notify the user the copy is complete. However, while that alert is displayed, I can see the div still being updated with item numbers, meaning it is still doing the copying. I need this alert to only display when all of the items have been written. If the user dismisses the alert, there could be items that were not copied because they were still queued up.

Am I using the promise incorrectly in my code? How can I insure that the function updateSuccessFinal is not called until all items have been written?

I am using d = $.Deferred() inside my addItem function. I have also tried moving that outside the loop and passing d to addItem and that did not seem to make any difference.

function snapshotItems(sourceListName, queryText, targetListName) 
{
    // Snapshot all of the items picked up in the criteria of view view into the snapshot list
    var context = new SP.ClientContext.get_current();
    var srcList = context.get_web().get_lists().getByTitle(sourceListName);

    // Create query
    var query = new SP.CamlQuery();
    query.set_viewXml(queryText);

    // Items matching the query
    var items = srcList.getItems(query);
    context.load(items);

    // Insert the rows in the Snapshot table
    context.executeQueryAsync(
        function()
        {
            var listEnumerator = items.getEnumerator();
            var i = 0;
            while (listEnumerator.moveNext())
            {
                var curListItem = listEnumerator.get_current();
                addItem(curListItem, i).done(
                    function() {
                    }
                );
                i++;
            }
            snapshotSuccessFinal();
        },
        function(sender, args)
        {
            alert('Get snapshot query failed: ' + args.get_message());
        }
    );

    function addItem(curListItem, i)
    {
        // Need to use promises because batching all the adds results in a "message too large" error from SharePoint
        // So uses promises to execute one add at a time
        var d = $.Deferred();

        var context = new SP.ClientContext.get_current();
        var targetList = context.get_web().get_lists().getByTitle(targetListName);

        var itemCreateInfo = new SP.ListItemCreationInformation();
        var listItem = targetList.addItem(itemCreateInfo);

        // Copy the custom fields
        listItem.set_item("Title", curListItem.get_item("Title"));

        // Write it     
        listItem.update();
        context.load(listItem);
        var passedData = { dfd: d, idx: i };
        context.executeQueryAsync(Function.createDelegate(passedData,onUpdateOneSuccess), Function.createDelegate(passedData,onUpdateOneFailed));
        return d.promise();
    }

    function onUpdateOneSuccess()
    {
        $("workingDiv").text("Snapshot item #" + this.idx.toString());
        this.dfd.resolve();
    }

    function onUpdateOneFailed()
    {
        alert("Add item failed: " + args.get_message())
    }
}

function snapshotSuccessFinal()
{
    $("workingDiv").text("Snapshot populated.");
    alert("Snapshot created successfully.");
}

1 Answer 1

2

At first glance (and its late saturday afternoon here, and the sun is out and its above 20 celcius for the first time, and I have had some beer)

You are running into JavaScrip scoping issues:

var passedData = { dfd: d, idx: i };

is the line that worries me, (it is almost a philosophical question... but that must be the beer)
where does i come from?

You are passing it so it will be the 'this' scope in the succesfunction. Which is highlevel JavaScript but not required here if you move the onUpdate function declarations inside the addItem function... that way those variables will be available and you don't have to pass a scope.

...that is the problem I have with your i

Move additem inside the calling function, where i is declared, and I think (can't be sure because of another beer) your problem is solved.

But Then

Promises are great, and advanced as well, but not really required here as all you use them for is waiting. This can be done with JavaScript 'executor' Functions in ES6

I think you can go back to the old (and simpler) POP pattern in this case.

Have your Iterator loop store all information as Objects in an Array. Then loop over the Array, make your addItem function POP a value, set executeQuery at work, and in the successFunction call addItem (recursion!) again ONLY IF the Array is not empty yet.

pseudo code:

var update=[1,2,3,4,5];

function additem(){
    var item;

    function handleerror(){ //so item is available in here!
      //cool! We can handle an error
      //even add the (modified) item with update.push(item)
      //and continue with:
      additem();
    }

    if(update.length>0){
        item=update.pop();
        //add item
        executequeryAsync(additem , handleerror );
    } else {
 //https://msdn.microsoft.com/en-us/library/office/ff412058(v=office.14).aspx
        SP.UI.Status.addStatus('Add items', 'All Done!');
    }
}

PS. If you do want to pass Scopes, ditch Function.CreateDelegate (which is old IE-ancient Microsoft only code) and learn .bind() (which Function.CreateDelegate does for you you under the hood because SharePoint needed to be backwards compatible with IE-ancient)

Differing ways to make executeQueryAsync calls - Function.createDelegate (is old IE8 code)

PS2. I am on motorbike roadtrip in the USA in September, if you are anywhere between Denver and Washington, or Ignite in Atlanta, you could buy me a beer :-)

5
  • Thanks for your detailed response! I just tried moving addItem inside the calling function as you suggested, along with the success/failed functions. However, I am still getting the alert "Snapshot created successfully" but seeing the div continue to update. I'll keep playing with it, or try your other suggested method if I have time to rip it apart.
    – mannaggia
    Commented May 7, 2016 at 19:28
  • PS... I will be at Ignite in Atlanta!
    – mannaggia
    Commented May 7, 2016 at 19:34
  • ... hangover ... learn to trace to the F12 DEV console with console.log. console.info and all his friends. edit your question and show us your new code structure Commented May 8, 2016 at 8:03
  • Yep, I use F12 all the time. Async calls make it a little difficult to trace. I ended up taking your "but then" suggestion, and used an array and recursive call, and it (appears) to work beautifully. I like that method more because (in addition to it working) I am not quite up to speed with the async calls and promises, and due to time constraints, I went down the road of guessing and trial-and-error, rather than understanding how all that works. I may come back to it later after I get more knowledgeable with promises. Appreciate it!
    – mannaggia
    Commented May 9, 2016 at 0:30
  • If you accept the answer we all get a better credit rate, and your question goes on the "Answered" pile Commented May 9, 2016 at 8:06

Your Answer

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

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