Domanda

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.");
}
È stato utile?

Soluzione

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 :-)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a sharepoint.stackexchange
scroll top